Vue 3 样式绑定完全指南
本文档从零开始讲解 Vue 3 的样式绑定:如何用数据动态控制 class 和 style,以及单文件组件里的 scoped、深度选择器、在 CSS 里使用响应式数据 等,配有大量示例,适合新手系统学习。
一、为什么需要“样式绑定”?
1.1 静态 vs 动态
写死 class 或 style 很简单:
<p class="title">标题</p>
<p style="color: red;">红色文字</p>
但实际开发里,样式经常要跟着数据变,例如:
- 当前选中的 Tab 要高亮(加个
activeclass) - 主题切换(亮色/暗色,整页的 class 或 CSS 变量要变)
- 根据状态显示不同颜色(成功绿、失败红)
- 根据后端返回的数值动态设置宽度、颜色
在 Vue 里,用 :class 和 :style(即 v-bind:class / v-bind:style)把数据和样式绑在一起:数据变,样式自动变。
二、绑定 class(:class)
2.1 对象语法:按条件加 class
语法: :class="{ 类名: 条件 }"
对象的 键 是 class 名,值 是“是否加上这个 class”的条件(真则加,假则不加)。
<template>
<div>
<p :class="{ active: isActive, 'text-danger': hasError }">段落</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const isActive = ref(true)
const hasError = ref(false)
</script>
<style scoped>
.active {
font-weight: bold;
}
.text-danger {
color: red;
}
</style>
isActive为 true → 有activeclass。hasError为 true → 有text-dangerclass。- 类名里有横线(如
text-danger)要加引号,否则会被当成减号。
多个条件、一个 class:
<p :class="{ active: isActive && isValid }">段落</p>
2.2 对象语法:直接绑定一个对象变量
不必把对象写在模板里,可以放在 script 里(或计算属性),更清晰:
<template>
<p :class="classObject">段落</p>
</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>
2.3 数组语法:多个 class 一起绑
语法: :class="[类名1, 类名2, ...]"
数组里可以是字符串(固定 class)、变量、或对象(按条件加)。最终会合并成一个 class 列表。
<template>
<div>
<p :class="[classA, classB]">段落</p>
<p :class="[classA, { active: isActive }]">段落</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const classA = ref('title')
const classB = ref('bold')
const isActive = ref(true)
</script>
- 第一个
<p>:class 为title bold。 - 第二个
<p>:class 为title,并且若isActive为 true 还会加active。
2.4 数组 + 条件:用三元或对象
<template>
<p :class="[baseClass, isActive ? 'active' : '']">段落</p>
<p :class="[baseClass, { active: isActive, error: hasError }]">段落</p>
</template>
<script setup>
import { ref } from 'vue'
const baseClass = ref('box')
const isActive = ref(true)
const hasError = ref(false)
</script>
2.5 和静态 class 一起写
:class 可以和静态的 class 共存,最终会合并:
<p class="static-title" :class="{ active: isActive }">段落</p>
渲染结果可能是:class="static-title active"(当 isActive 为 true 时)。
三、绑定内联 style(:style)
3.1 对象语法:键为 CSS 属性,值为样式值
语法: :style="{ 属性名: 值 }"
属性名可以用 驼峰(如 fontSize)或短横线(需引号,如 'font-size')。
值为字符串或数字;数字会默认加单位 px(部分属性如 opacity 不加)。
<template>
<div>
<p :style="{ color: textColor, fontSize: fontSize + 'px' }">彩色文字</p>
<p :style="{ color: 'blue', 'font-size': '20px' }">蓝色大字</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const textColor = ref('red')
const fontSize = ref(16)
</script>
- 第一个:颜色、字号来自变量;字号若想用数字自动加 px,可写
fontSize: fontSize(Vue 会加 px)。 - 第二个:短横线属性名要加引号。
3.2 自动加 px(数字型属性)
多数数字类型的 CSS 属性,Vue 会自动加 px:
<p :style="{ width: 100, height: 50 }">盒子</p>
渲染为 style="width: 100px; height: 50px;"。
若需要 rem、%、em 等,自己写成字符串即可:width: '10rem'。
3.3 绑定一个 style 对象变量
和 class 一样,可以把对象放在 script 或计算属性里:
<template>
<p :style="styleObject">段落</p>
</template>
<script setup>
import { ref } from 'vue'
const styleObject = ref({
color: 'red',
fontSize: '18px',
backgroundColor: 'lightyellow'
})
</script>
3.4 数组语法:多个 style 对象合并
语法: :style="[对象1, 对象2, ...]"
后面的对象会覆盖前面同名字段,适合“基础样式 + 覆盖样式”:
<template>
<p :style="[baseStyle, overrideStyle]">段落</p>
</template>
<script setup>
import { ref } from 'vue'
const baseStyle = ref({
color: 'blue',
fontSize: '14px'
})
const overrideStyle = ref({
fontSize: '20px'
})
</script>
最终 fontSize 为 20px,color 仍为 blue。
3.5 需要浏览器前缀时
某些属性需要前缀(如 transform),可以写多个键,Vue 会根据需要处理;复杂情况也可在对象里写带前缀的键:
<p :style="{ transform: 'scale(1.1)' }">放大</p>
Vue 会对需要前缀的属性自动加前缀(如 -webkit-transform 等)。
四、class 与 style 绑定小结
| 类型 | 写法示例 |
|---|---|
| class 对象 | :class="{ active: isActive }" |
| class 数组 | :class="[classA, classB]" |
| class 数组+对象 | :class="[classA, { active: isActive }]" |
| style 对象 | :style="{ color: c, fontSize: size + 'px' }" |
| style 数组 | :style="[baseStyle, overrideStyle]" |
| 与静态共存 | class="static" :class="dynamic"、style="..." :style="..." |
五、单文件组件里的 与 scoped
5.1 普通 :全局样式
在 .vue 里写的 <style> 默认是全局的,会影响整个项目里匹配到的元素。
一般只在不带 scoped 且确实需要影响全局时使用。
<style>
.global-title {
font-size: 24px;
}
</style>
5.2 scoped:只影响当前组件
加上 scoped 后,Vue 会给当前组件的元素加上一个唯一属性(如 data-v-xxx),选择器会变成“只匹配带这个属性的元素”,从而只作用于当前组件的模板,不污染其它组件。
<template>
<p class="desc">只有本组件的 p 会变红</p>
</template>
<style scoped>
.desc {
color: red;
}
</style>
注意: 子组件根元素会同时拥有父组件的 scoped 属性和自己的 scoped 属性,所以父组件的 scoped 选择器可以选中子组件根元素;但默认选不中子组件内部的元素。若要影响子组件内部,要用深度选择器(见下一小节)。
5.3 深度选择器:影响子组件内部
当你想在父组件的 scoped 样式中,修改子组件内部的样式时,要用 深度选择器,否则选择器会被加上 [data-v-xxx],匹配不到子组件里的 DOM。
Vue 3 推荐写法: :deep(选择器)
<template>
<Child class="my-child" />
</template>
<style scoped>
.my-child :deep(.inner) {
color: blue;
}
</style>
这样 .inner 即使写在子组件里,也会被父组件的这段样式影响。
其它写法(如 ::v-deep、/deep/)在 Vue 3 中请用 :deep() 替代。
六、在 里使用响应式数据(v-bind)
Vue 3.2+ 支持在 <style> 里用 v-bind 绑定响应式数据,实现“数据变,CSS 值也跟着变”(如主题色、尺寸)。
语法: 在 style 里写 属性: v-bind(变量名) 或 属性: v-bind('变量名')。
<template>
<div class="box">根据数据变色的盒子</div>
</template>
<script setup>
import { ref } from 'vue'
const themeColor = ref('blue')
</script>
<style scoped>
.box {
width: 100px;
height: 100px;
background-color: v-bind(themeColor);
}
</style>
- themeColor 在 script 里改变时,
.box的背景色会同步更新。 - 若变量名包含短横线等,可用字符串:
v-bind('theme-color')。
适用场景: 主题色、动态尺寸、根据后端配置改样式等。
七、常见场景示例
7.1 Tab 高亮(当前项加 active class)
<template>
<div class="tabs">
<span
v-for="tab in tabs"
:key="tab.id"
:class="{ active: currentTab === tab.id }"
@click="currentTab = tab.id"
>
{{ tab.name }}
</span>
</div>
</template>
<script setup>
import { ref } from 'vue'
const currentTab = ref('a')
const tabs = ref([
{ id: 'a', name: 'Tab A' },
{ id: 'b', name: 'Tab B' }
])
</script>
<style scoped>
.tabs span {
padding: 8px 16px;
cursor: pointer;
}
.tabs .active {
color: blue;
border-bottom: 2px solid blue;
}
</style>
7.2 根据状态显示不同颜色(成功/失败/进行中)
<template>
<span :class="statusClass">{{ statusText }}</span>
</template>
<script setup>
import { computed, ref } from 'vue'
const status = ref('success') // 'success' | 'error' | 'pending'
const statusClass = computed(() => ({
success: status.value === 'success',
error: status.value === 'error',
pending: status.value === 'pending'
}))
const statusText = computed(() => ({
success: '成功',
error: '失败',
pending: '进行中'
}[status.value]))
</script>
<style scoped>
.success { color: green; }
.error { color: red; }
.pending { color: orange; }
</style>
7.3 主题切换(用 CSS 变量 + v-bind)
<template>
<div class="page">
<p>页面内容</p>
<button @click="toggle">切换主题</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const isDark = ref(false)
const theme = ref({
bg: '#fff',
text: '#333'
})
function toggle() {
isDark.value = !isDark.value
theme.value = isDark.value
? { bg: '#222', text: '#eee' }
: { bg: '#fff', text: '#333' }
}
</script>
<style scoped>
.page {
background-color: v-bind('theme.bg');
color: v-bind('theme.text');
padding: 20px;
}
</style>
(v-bind 支持简单对象路径,具体以 Vue 版本为准;若不行可把 theme.bg 拆成两个 ref 再 v-bind。)
7.4 进度条宽度随数据变化
<template>
<div class="progress-bar">
<div class="inner" :style="{ width: percent + '%' }"></div>
</div>
<p>进度:{{ percent }}%</p>
</template>
<script setup>
import { ref } from 'vue'
const percent = ref(60)
</script>
<style scoped>
.progress-bar {
height: 8px;
background: #eee;
border-radius: 4px;
overflow: hidden;
}
.progress-bar .inner {
height: 100%;
background: blue;
transition: width 0.3s;
}
</style>
7.5 列表项“选中”样式(:class + 数组)
<template>
<ul>
<li
v-for="item in list"
:key="item.id"
:class="['item', { selected: selectedId === item.id }]"
@click="selectedId = item.id"
>
{{ item.name }}
</li>
</ul>
</template>
<script setup>
import { ref } from 'vue'
const selectedId = ref(1)
const list = ref([
{ id: 1, name: '选项一' },
{ id: 2, name: '选项二' }
])
</script>
<style scoped>
.item { padding: 8px; cursor: pointer; }
.item.selected { background: #e6f7ff; }
</style>
八、易错点与注意点
8.1 class 名有横线必须加引号
:class="{ 'text-danger': true }" 正确;:class="{ text-danger: true }" 会报错或解析错误,因为 text-danger 会被当成减号运算。
8.2 :style 里数字默认加 px
不需要 px 的属性(如 opacity、zIndex)写数字即可;需要 rem、% 等要自己写字符串:fontSize: '1.2rem'。
8.3 scoped 只影响当前组件模板
子组件内部的类名,父组件用普通选择器选不中,要用 :deep(选择器) 才能样式穿透。
8.4 多个 class/style 会合并,不覆盖
同时写 class 和 :class、style 和 :style,Vue 会合并,不会整段覆盖。
同名字段在 :style 数组里以后面的为准。
九、样式绑定速查表
| 需求 | 写法 |
|---|---|
| 按条件加 class | :class="{ active: isActive }" |
| 多个 class | :class="[a, b]" 或 :class="[a, { active: isActive }]" |
| 动态 style | :style="{ color: c, fontSize: size + 'px' }" |
| 多个 style 对象 | :style="[base, override]" |
| 组件内私有样式 | <style scoped> |
| 影响子组件内部 | :deep(.子类名) |
| CSS 用响应式数据 | 属性: v-bind(变量) |
十、学习建议
- 先练熟 :class 的对象和数组写法,能根据布尔值、当前选中项加 class。
- 再练 :style 的对象写法,能根据数据改颜色、尺寸、进度等。
- 单文件组件里默认用
<style scoped>,需要改子组件内部再用 :deep()。 - 需要“主题、动态色/尺寸”时,可配合 v-bind in CSS(Vue 3.2+)或 CSS 变量。
把本文档里的示例在项目里改一改数据看样式变化,会掌握得更牢。祝你学习顺利。