Vue3 Class 和 Style 绑定详解
核心概念理解
为什么需要动态绑定?
在实际开发中,我们经常需要根据数据状态来改变元素的样式,比如:
- 用户登录状态显示不同的按钮样式
- 表单验证结果显示不同颜色
- 列表项选中状态的高亮显示
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>
使用建议
- 样式复杂 → 使用 CSS 类 + Class 绑定
- 样式简单 → 使用 Style 绑定
- 需要缓存 → 使用计算属性
- 动态计算 → 使用方法
记忆口诀:
- Class 绑定:控制哪些类名生效
- Style 绑定:直接设置样式属性
- 对象语法:键是类名/样式名,值是布尔值/具体值
- 数组语法:列出要应用的类名/样式对象