基本介绍
在 Vue 3.2+ 版本中,我们可以直接在 <style>标签中使用响应式的 JavaScript 变量,这被称为 CSS 变量注入 或 v-bind in CSS。这个功能让我们能够:
-
动态修改样式:在脚本中定义的响应式变量可以直接在样式中引用
-
减少样式类切换:不再需要为不同的状态定义多个 CSS 类
-
实现主题切换:轻松实现动态主题色切换功能
-
响应式设计:让 CSS 能够响应组件状态的变化
简单说,就是让你在 CSS 中直接使用 Vue 的响应式数据,当数据变化时,样式会自动更新!
简单示例-黑色-亮色主题切换
xml
<template>
<div class="base-case-container">
<p>动态主题切换示例</p>
<button @click="toggle()">切换主题</button>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
import { useToggle } from "@vueuse/core";
const [isDarkMode, toggle] = useToggle();
const fontSize = ref<string>("16px");
const textColor = computed(() => (isDarkMode.value ? "#ffffff" : "#333333"));
const backgroundColor = computed(() =>
isDarkMode.value ? "#1a1a1a" : "#f5f5f5"
);
</script>
<style lang="scss">
.base-case-container {
width: 100%;
height: 100%;
font-size: v-bind(fontSize);
color: v-bind(textColor);
background-color: v-bind(backgroundColor);
padding: 20px;
transition: all 0.3s ease;
}
</style
项目实战-动态主题切换系统
xml
<template>
<div class="theme-container" :style="customStyles">
<header class="header">
<h1>动态主题系统</h1>
<div class="theme-controls">
<div class="color-picker">
<label>主色:</label>
<input type="color" v-model="theme.primary" />
</div>
<div class="slider-control">
<label>字体大小:{{ theme.fontSize }}px</label>
<input type="range" v-model="theme.fontSize" min="12" max="24" />
</div>
<div class="slider-control">
<label>圆角:{{ theme.borderRadius }}px</label>
<input type="range" v-model="theme.borderRadius" min="0" max="20" />
</div>
</div>
</header>
<main class="content">
<button class="btn btn-primary">主要按钮</button>
<button class="btn btn-secondary">次要按钮</button>
<div class="card">
<h3>卡片标题</h3>
<p>这是一个使用动态主题的卡片组件</p>
</div>
<div class="alert">这是一个提示信息</div>
</main>
</div>
</template>
<script setup>
import { reactive, computed } from "vue";
// 主题配置
const theme = reactive({
primary: "#4a90e2",
secondary: "#7b61ff",
fontSize: 16,
borderRadius: 8,
spacing: 20,
});
// 计算其他衍生颜色
const themeColors = computed(() => {
return {
primaryLight: lightenColor(theme.primary, 30),
primaryDark: darkenColor(theme.primary, 20),
secondaryLight: lightenColor(theme.secondary, 30),
};
});
// 工具函数:颜色变亮
function lightenColor(color, percent) {
// 1. 移除#号,将十六进制转为十进制整数
const num = parseInt(color.replace('#', ''), 16) // "#4a90e2" → 0x4a90e2 = 4890850
// 2. 计算调整量
// 255的百分比 → 每个颜色分量最大可增加的值
const amt = Math.round(2.55 * percent) // 例如percent=30 → 2.55 * 30=76.5 → 77
// 3. 分解RGB分量
const R = (num >> 16) + amt // 红色分量:右移16位
const G = (num >> 8 & 0x00FF) + amt // 绿色分量:右移8位后与0x00FF进行与运算
const B = (num & 0x0000FF) + amt // 蓝色分量:与0x0000FF进行与运算
// 4. 确保分量在0-255范围内
return '#' + (
0x1000000 + // 保证结果始终是7位十六进制数
// 三元运算符确保值在有效范围内
(R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 + // 红色分量
(G < 255 ? G < 1 ? 0 : G : 255) * 0x100 + // 绿色分量
(B < 255 ? B < 1 ? 0 : B : 255) // 蓝色分量
).toString(16).slice(1) // 转换为十六进制并去掉开头的1
}
// 工具函数:颜色变暗
function darkenColor(color, percent) {
// 1. 十六进制转十进制
const num = parseInt(color.replace('#', ''), 16)
// 2. 计算减少量
const amt = Math.round(2.55 * percent) // 百分比转换为实际减少值
// 3. 减少每个分量
const R = (num >> 16) - amt
const G = (num >> 8 & 0x00FF) - amt
const B = (num & 0x0000FF) - amt
// 4. 确保值不小于0
return '#' + (
0x1000000 +
(R > 0 ? R : 0) * 0x10000 + // 如果R>0,使用R,否则为0
(G > 0 ? G : 0) * 0x100 +
(B > 0 ? B : 0)
).toString(16).slice(1)
}
</script>
<style scoped>
.theme-container {
/* 直接使用响应式变量 */
--primary-color: v-bind("theme.primary");
--secondary-color: v-bind("theme.secondary");
--font-size: v-bind('theme.fontSize + "px"');
--border-radius: v-bind('theme.borderRadius + "px"');
--spacing: v-bind('theme.spacing + "px"');
/* 使用计算属性 */
--primary-light: v-bind("themeColors.primaryLight");
--primary-dark: v-bind("themeColors.primaryDark");
--secondary-light: v-bind("themeColors.secondaryLight");
padding: var(--spacing);
min-height: 100vh;
font-size: var(--font-size);
transition: all 0.3s ease;
.header {
margin-bottom: calc(var(--spacing) * 2);
padding-bottom: var(--spacing);
border-bottom: 2px solid var(--primary-light);
.theme-controls {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--spacing);
margin-top: var(--spacing);
}
}
}
.content {
.btn {
padding: 12px 24px;
border: none;
border-radius: var(--border-radius);
font-size: calc(var(--font-size) * 0.875);
cursor: pointer;
transition: all 0.2s ease;
margin: 8px;
&.btn-primary {
background-color: var(--primary-color);
color: white;
}
&.btn-primary:hover {
background-color: var(--primary-dark);
transform: translateY(-2px);
}
&.btn-secondary {
background-color: var(--secondary-color);
color: white;
}
&.btn-secondary:hover {
background-color: var(--secondary-light);
}
}
.card {
background: white;
border-radius: var(--border-radius);
padding: var(--spacing);
margin: var(--spacing) 0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border-left: 4px solid var(--primary-color);
}
.alert {
background-color: var(--primary-light);
border: 1px solid var(--primary-color);
border-radius: var(--border-radius);
padding: calc(var(--spacing) * 0.75);
margin: var(--spacing) 0;
color: var(--primary-dark);
}
}
</style>
注意事项和最佳实践
性能考虑
xml
<script setup>
// 避免频繁更新的变量用于样式绑定
// 不推荐:频繁变化的值
const scrollPosition = ref(0) // 滚动时频繁变化
// 推荐:变化不频繁的值
const themeColor = ref('#3498db')
const fontSize = ref(16)
</script>
兼容性处理
xml
<style scoped>
/* 提供回退方案 */
.element {
color: #3498db; /* 回退值 */
color: v-bind(themeColor);
/* 对于不支持 CSS 变量的浏览器 */
@supports not (color: v-bind(themeColor)) {
/* 备用样式 */
}
}
</style>
结合 CSS 自定义属性
xml
<style scoped>
/* 在 :root 或组件中定义 CSS 变量 */
.component {
--primary-color: v-bind(primaryColor);
--secondary-color: v-bind(secondaryColor);
/* 在组件内重复使用 */
.child {
color: var(--primary-color);
}
.another-child {
background-color: var(--secondary-color);
}
}
</style>
总结
Vue 样式中的 JavaScript 变量功能为前端开发带来了革命性的变化:
-
更灵活的样式控制:样式可以响应数据变化
-
代码更简洁:减少样式类切换的逻辑
-
主题系统更强大:轻松实现动态主题
-
更好的开发体验:在 Vue 单文件组件中实现样式逻辑一体化