Vue3样式绑定

Vue 3 样式绑定完全指南

本文档从零开始讲解 Vue 3 的样式绑定:如何用数据动态控制 classstyle,以及单文件组件里的 scoped深度选择器在 CSS 里使用响应式数据 等,配有大量示例,适合新手系统学习。


一、为什么需要“样式绑定”?

1.1 静态 vs 动态

写死 class 或 style 很简单:

<p class="title">标题</p>
<p style="color: red;">红色文字</p>

但实际开发里,样式经常要跟着数据变,例如:

  • 当前选中的 Tab 要高亮(加个 active class)
  • 主题切换(亮色/暗色,整页的 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 → 有 active class。
  • hasError 为 true → 有 text-danger class。
  • 类名里有横线(如 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 的属性(如 opacityzIndex)写数字即可;需要 rem、% 等要自己写字符串:fontSize: '1.2rem'

8.3 scoped 只影响当前组件模板

子组件内部的类名,父组件用普通选择器选不中,要用 :deep(选择器) 才能样式穿透。

8.4 多个 class/style 会合并,不覆盖

同时写 class:classstyle: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(变量)

十、学习建议

  1. 先练熟 :class 的对象和数组写法,能根据布尔值、当前选中项加 class。
  2. 再练 :style 的对象写法,能根据数据改颜色、尺寸、进度等。
  3. 单文件组件里默认用 <style scoped>,需要改子组件内部再用 :deep()
  4. 需要“主题、动态色/尺寸”时,可配合 v-bind in CSS(Vue 3.2+)或 CSS 变量。

把本文档里的示例在项目里改一改数据看样式变化,会掌握得更牢。祝你学习顺利。

发表评论