Vue3 Class 和 Style 绑定详解

Vue3 Class 和 Style 绑定详解

参考 Class 与 Style 绑定 | Vue.js

核心概念理解

为什么需要动态绑定?

在实际开发中,我们经常需要根据数据状态来改变元素的样式,比如:

  • 用户登录状态显示不同的按钮样式
  • 表单验证结果显示不同颜色
  • 列表项选中状态的高亮显示

Class 绑定

1. 对象语法 - 最常用

vue 复制代码
<template>
  <div>
    <!-- 基础对象语法 -->
    <div :class="{ active: isActive, 'text-danger': hasError }">
      动态样式示例
    </div>
    
    <!-- 结合普通 class -->
    <div class="static-class" :class="{ active: isActive, 'text-danger': hasError }">
      混合使用
    </div>
    
    <!-- 使用计算属性 -->
    <div :class="classObject">
      计算属性方式
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const isActive = ref(true)
const hasError = ref(false)

// 计算属性方式 - 更清晰
const classObject = computed(() => ({
  active: isActive.value,
  'text-danger': hasError.value
}))
</script>

<style>
.active {
  background-color: #42b983;
  color: white;
}

.text-danger {
  color: red;
  font-weight: bold;
}

.static-class {
  padding: 10px;
  border: 1px solid #ccc;
}
</style>

2. 数组语法

vue 复制代码
<template>
  <div>
    <!-- 基础数组语法 -->
    <div :class="[activeClass, errorClass]">
      数组语法示例
    </div>
    
    <!-- 数组中使用三元表达式 -->
    <div :class="[isActive ? activeClass : '', errorClass]">
      条件渲染
    </div>
    
    <!-- 数组中使用对象语法 -->
    <div :class="[{ active: isActive }, errorClass]">
      混合使用
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const isActive = ref(true)
const activeClass = ref('active')
const errorClass = ref('text-danger')
</script>

3. 实际应用示例

vue 复制代码
<template>
  <div class="demo-container">
    <h2>任务列表</h2>
    
    <div class="task-list">
      <div 
        v-for="task in filteredTasks" 
        :key="task.id"
        :class="taskClassObject(task)"
        @click="toggleTask(task)"
      >
        <span class="task-text">{{ task.text }}</span>
        <span class="task-status">{{ task.completed ? '✅' : '⭕' }}</span>
      </div>
    </div>
    
    <div class="controls">
      <button 
        :class="{ active: filter === 'all' }"
        @click="setFilter('all')"
      >
        全部
      </button>
      <button 
        :class="{ active: filter === 'active' }"
        @click="setFilter('active')"
      >
        未完成
      </button>
      <button 
        :class="{ active: filter === 'completed' }"
        @click="setFilter('completed')"
      >
        已完成
      </button>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const tasks = ref([
  { id: 1, text: '学习 Vue3', completed: true },
  { id: 2, text: '完成项目', completed: false },
  { id: 3, text: '写文档', completed: false }
])

const filter = ref('all')

// 为每个任务计算样式类
const taskClassObject = (task) => ({
  'task-item': true,
  'task-completed': task.completed,
  'task-active': !task.completed
})

const toggleTask = (task) => {
  task.completed = !task.completed
}

const setFilter = (newFilter) => {
  filter.value = newFilter
}

// 过滤后的任务
const filteredTasks = computed(() => {
  switch (filter.value) {
    case 'active':
      return tasks.value.filter(task => !task.completed)
    case 'completed':
      return tasks.value.filter(task => task.completed)
    default:
      return tasks.value
  }
})
</script>

<style>
.demo-container {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

.task-list {
  margin-bottom: 20px;
}

.task-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 15px;
  margin: 10px 0;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.3s ease;
  border: 2px solid transparent;
}

.task-active {
  background-color: #e3f2fd;
  border-color: #2196f3;
}

.task-completed {
  background-color: #e8f5e9;
  border-color: #4caf50;
  opacity: 0.7;
}

.task-text {
  font-size: 16px;
}

.task-status {
  font-size: 18px;
}

.controls {
  display: flex;
  gap: 10px;
}

.controls button {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  background-color: #f5f5f5;
  cursor: pointer;
  transition: all 0.2s ease;
}

.controls button:hover {
  background-color: #e0e0e0;
}

.controls button.active {
  background-color: #1976d2;
  color: white;
}
</style>

Style 绑定

1. 对象语法

vue 复制代码
<template>
  <div>
    <!-- 基础对象语法 -->
    <div :style="{ color: activeColor, fontSize: fontSize + 'px' }">
      动态样式文本
    </div>
    
    <!-- 使用样式对象 -->
    <div :style="styleObject">
      样式对象方式
    </div>
    
    <!-- 多个样式对象 -->
    <div :style="[baseStyles, overridingStyles]">
      多个样式对象
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const activeColor = ref('red')
const fontSize = ref(30)

// 样式对象
const styleObject = ref({
  color: 'blue',
  fontSize: '20px',
  fontWeight: 'bold'
})

// 多个样式对象
const baseStyles = ref({
  color: 'green',
  padding: '10px'
})

const overridingStyles = ref({
  color: 'purple', // 会覆盖 baseStyles 中的 color
  margin: '5px'
})
</script>

2. 数组语法

vue 复制代码
<template>
  <div>
    <!-- 数组语法 -->
    <div :style="[baseStyle, { color: activeColor }]">
      数组样式绑定
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const activeColor = ref('red')

const baseStyle = ref({
  fontSize: '24px',
  padding: '20px',
  border: '1px solid #ccc'
})
</script>

3. 实际应用示例

vue 复制代码
<template>
  <div class="style-demo">
    <h2>动态样式示例</h2>
    
    <!-- 颜色选择器 -->
    <div class="color-picker">
      <label>选择颜色:</label>
      <input type="color" v-model="selectedColor">
    </div>
    
    <!-- 字体大小调节 -->
    <div class="size-control">
      <label>字体大小: {{ fontSize }}px</label>
      <input 
        type="range" 
        min="12" 
        max="48" 
        v-model="fontSize"
      >
    </div>
    
    <!-- 动态文本展示 -->
    <div 
      class="dynamic-text"
      :style="textStyles"
    >
      这是动态样式文本
    </div>
    
    <!-- 进度条示例 -->
    <div class="progress-container">
      <div 
        class="progress-bar"
        :style="progressStyles"
      >
        {{ progress }}%
      </div>
    </div>
    <input 
      type="range" 
      min="0" 
      max="100" 
      v-model="progress"
    >
    
    <!-- 主题切换 -->
    <div class="theme-toggle">
      <button 
        v-for="theme in themes" 
        :key="theme.name"
        :style="{ backgroundColor: theme.bgColor, color: theme.textColor }"
        @click="applyTheme(theme)"
      >
        {{ theme.name }}
      </button>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const selectedColor = ref('#42b983')
const fontSize = ref(24)
const progress = ref(50)

// 主题数据
const themes = ref([
  { name: '默认', bgColor: '#f0f0f0', textColor: '#333' },
  { name: '暗黑', bgColor: '#333', textColor: '#fff' },
  { name: '海洋', bgColor: '#42b983', textColor: '#fff' },
  { name: '热情', bgColor: '#ff6b6b', textColor: '#fff' }
])

const currentTheme = ref(themes.value[0])

// 计算文本样式
const textStyles = computed(() => ({
  color: selectedColor.value,
  fontSize: fontSize.value + 'px',
  fontWeight: 'bold',
  textAlign: 'center',
  padding: '20px',
  borderRadius: '8px',
  backgroundColor: 'rgba(0,0,0,0.1)'
}))

// 计算进度条样式
const progressStyles = computed(() => ({
  width: progress.value + '%',
  backgroundColor: selectedColor.value,
  height: '40px',
  lineHeight: '40px',
  textAlign: 'center',
  color: 'white',
  borderRadius: '4px',
  transition: 'width 0.3s ease'
}))

// 应用主题
const applyTheme = (theme) => {
  currentTheme.value = theme
  document.body.style.backgroundColor = theme.bgColor
  document.body.style.color = theme.textColor
}
</script>

<style>
.style-demo {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

.color-picker, .size-control, .theme-toggle {
  margin: 20px 0;
  padding: 15px;
  border: 1px solid #ddd;
  border-radius: 8px;
}

.color-picker label, .size-control label {
  display: block;
  margin-bottom: 10px;
  font-weight: bold;
}

.progress-container {
  width: 100%;
  height: 40px;
  background-color: #f0f0f0;
  border-radius: 4px;
  margin: 20px 0;
  overflow: hidden;
}

.progress-bar {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: width 0.3s ease;
}

.theme-toggle {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.theme-toggle button {
  padding: 10px 15px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: transform 0.2s ease;
}

.theme-toggle button:hover {
  transform: translateY(-2px);
}
</style>

高级用法示例

1. 条件样式绑定

vue 复制代码
<template>
  <div class="advanced-demo">
    <h2>高级样式绑定</h2>
    
    <!-- 根据状态动态切换样式 -->
    <div 
      class="status-card"
      :class="{
        'status-success': status === 'success',
        'status-warning': status === 'warning',
        'status-error': status === 'error',
        'status-loading': status === 'loading'
      }"
    >
      <div class="status-content">
        <span class="status-icon">{{ statusIcons[status] }}</span>
        <span class="status-text">{{ statusMessages[status] }}</span>
      </div>
    </div>
    
    <!-- 按钮控制状态 -->
    <div class="status-controls">
      <button 
        v-for="(label, statusKey) in statusLabels" 
        :key="statusKey"
        :class="{ active: status === statusKey }"
        @click="setStatus(statusKey)"
      >
        {{ label }}
      </button>
    </div>
    
    <!-- 动态内联样式 -->
    <div 
      class="animated-box"
      :style="{
        backgroundColor: boxColor,
        transform: `rotate(${rotation}deg) scale(${scale})`,
        transition: 'all 0.5s ease'
      }"
    >
      动画盒子
    </div>
    
    <div class="animation-controls">
      <button @click="changeColor">改变颜色</button>
      <button @click="rotateBox">旋转</button>
      <button @click="scaleBox">缩放</button>
      <button @click="resetBox">重置</button>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const status = ref('success')
const rotation = ref(0)
const scale = ref(1)
const boxColor = ref('#42b983')

const statusLabels = {
  success: '成功',
  warning: '警告',
  error: '错误',
  loading: '加载中'
}

const statusIcons = {
  success: '✅',
  warning: '⚠️',
  error: '❌',
  loading: '⏳'
}

const statusMessages = {
  success: '操作成功完成!',
  warning: '请注意潜在问题',
  error: '操作失败,请重试',
  loading: '正在处理中...'
}

const setStatus = (newStatus) => {
  status.value = newStatus
}

const changeColor = () => {
  const colors = ['#42b983', '#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4']
  boxColor.value = colors[Math.floor(Math.random() * colors.length)]
}

const rotateBox = () => {
  rotation.value += 45
}

const scaleBox = () => {
  scale.value = scale.value === 1 ? 1.5 : 1
}

const resetBox = () => {
  rotation.value = 0
  scale.value = 1
  boxColor.value = '#42b983'
}
</script>

<style>
.advanced-demo {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.status-card {
  padding: 20px;
  border-radius: 8px;
  margin: 20px 0;
  text-align: center;
  transition: all 0.3s ease;
}

.status-success {
  background-color: #d4edda;
  border: 1px solid #c3e6cb;
  color: #155724;
}

.status-warning {
  background-color: #fff3cd;
  border: 1px solid #ffeaa7;
  color: #856404;
}

.status-error {
  background-color: #f8d7da;
  border: 1px solid #f5c6cb;
  color: #721c24;
}

.status-loading {
  background-color: #d1ecf1;
  border: 1px solid #bee5eb;
  color: #0c5460;
  animation: pulse 2s infinite;
}

@keyframes pulse {
  0% { opacity: 1; }
  50% { opacity: 0.7; }
  100% { opacity: 1; }
}

.status-content {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

.status-icon {
  font-size: 24px;
}

.status-text {
  font-size: 18px;
  font-weight: bold;
}

.status-controls {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin: 20px 0;
}

.status-controls button {
  padding: 10px 15px;
  border: none;
  border-radius: 4px;
  background-color: #f8f9fa;
  cursor: pointer;
  transition: all 0.2s ease;
}

.status-controls button:hover {
  background-color: #e9ecef;
}

.status-controls button.active {
  background-color: #007bff;
  color: white;
}

.animated-box {
  width: 200px;
  height: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 30px auto;
  border-radius: 8px;
  color: white;
  font-weight: bold;
  font-size: 18px;
}

.animation-controls {
  display: flex;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
}

.animation-controls button {
  padding: 10px 15px;
  border: none;
  border-radius: 4px;
  background-color: #6c757d;
  color: white;
  cursor: pointer;
  transition: all 0.2s ease;
}

.animation-controls button:hover {
  background-color: #5a6268;
  transform: translateY(-2px);
}
</style>

总结

Class 绑定语法

vue 复制代码
<!-- 对象语法 -->
<div :class="{ active: isActive, 'text-danger': hasError }"></div>

<!-- 数组语法 -->
<div :class="[activeClass, errorClass]"></div>

<!-- 混合使用 -->
<div :class="[{ active: isActive }, errorClass]"></div>

Style 绑定语法

vue 复制代码
<!-- 对象语法 -->
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

<!-- 数组语法 -->
<div :style="[baseStyles, overridingStyles]"></div>

使用建议

  1. 样式复杂 → 使用 CSS 类 + Class 绑定
  2. 样式简单 → 使用 Style 绑定
  3. 需要缓存 → 使用计算属性
  4. 动态计算 → 使用方法

记忆口诀

  • Class 绑定:控制哪些类名生效
  • Style 绑定:直接设置样式属性
  • 对象语法:键是类名/样式名,值是布尔值/具体值
  • 数组语法:列出要应用的类名/样式对象
相关推荐
跟橙姐学代码3 分钟前
Python 装饰器超详细讲解:从“看不懂”到“会使用”,一篇吃透
前端·python·ipython
pany21 分钟前
体验一款编程友好的显示器
前端·后端·程序员
Zuckjet26 分钟前
从零到百万:Notion如何用CRDT征服离线协作的终极挑战?
前端
GISBox31 分钟前
GISBox支持WMS协议的技术突破
vue.js·json·gis
ikonan31 分钟前
译:Chrome DevTools 实用技巧和窍门清单
前端·javascript
Juchecar32 分钟前
Vue3 v-if、v-show、v-for 详解及示例
前端·vue.js
ccc101835 分钟前
通过学长的分享,我学到了
前端
编辑胜编程35 分钟前
记录MCP开发表单
前端
可爱生存报告36 分钟前
vue3 vite quill-image-resize-module打包报错 Cannot set properties of undefined
前端·vite
__lll_36 分钟前
前端性能优化:Vue + Vite 全链路性能提升与打包体积压缩指南
前端·性能优化