学完这篇你能掌握 CSS 变量(自定义属性)的定义、调用、作用域覆盖和 JS 动态修改,能直接把一套颜色和间距抽成变量,做出可切换的暗色主题。
第一步:定义一个变量,看懂它的存放位置
这一步要做的,是在某个选择器里写下 --变量名: 值;,得到一个可以在别处引用的值。CSS 变量必须写在选择器内,不能裸写在样式表顶层;写在其中任何一个选择器里都算“定义成功”,只是作用范围不同。
:root {
--brand-color: #2f6fed;
--radius: 6px;
}
:root 等价于 html 元素,是放全局变量最常用的位置。变量名区分大小写,--Brand 和 --brand 是两个不同的变量。值可以是颜色、长度、字体名、calc() 表达式,甚至一段完整的多值声明(如 --shadow: 0 2px 8px rgba(0,0,0,.15))。
第二步:用 var() 取出来用,并给个兜底值
这一步要做的,是把变量塞进具体属性,得到真正生效的样式。语法是 属性: var(--变量名);,第二个参数可写回退值,变量不存在时用它顶上。
.btn {
background: var(--brand-color, #999);
border-radius: var(--radius, 4px);
box-shadow: var(--shadow, none);
}
回退值可以再套一个变量:var(--a, var(--b, red))。这在做“主题色可选、默认色兜底”时很好用。
注意:var() 写在属性值里,不能写在属性名、选择器、@media 条件里。@media (min-width: var(--bp)) 这种写法是无效的,媒体查询条件不支持变量。
第三步:利用继承覆盖,做出主题切换
这一步要做的,是在父级改一个值,让整块子树自动换装,得到不用改任何子元素样式就能换肤的效果。CSS 变量会被子元素继承,所以只要在某个容器上重新声明同名变量,容器内的所有引用都会跟着变。
:root {
--bg: #ffffff;
--text: #1a1a1a;
}
/* 给 html 加 dark 类就整体切暗色 */
html.dark {
--bg: #14161a;
--text: #e6e6e6;
}
body {
background: var(--bg);
color: var(--text);
}
局部覆盖同理:
.card--warning {
--brand-color: #e8a13a;
}
.card--warning 内部的按钮即使写的是 background: var(--brand-color),也会自动变成橙色。
注意:如果变量定义在 :root,但你在子元素里写 background: var(--bg, #fff),变量没取到时会用回退值,排查时容易误以为“变量没生效”。先在浏览器开发者工具的“计算样式”里确认变量值是否为空。
第四步:用 JS 读写变量,做实时调节
这一步要做的,是通过 document.documentElement.style.setProperty() 在运行时改变量值,得到不刷新页面就改变外观的效果。
// 读取(注意要写全 --)
const brand = getComputedStyle(document.documentElement)
.getPropertyValue('--brand-color')
.trim();
// 写入
document.documentElement.style.setProperty('--radius', '12px');
setProperty 控制的是内联样式,优先级高于 :root 里的声明,所以能覆盖。做“主题切换按钮”“字号调节滑块”“拖拽调色板”都靠它。
注意:getPropertyValue 返回的字符串可能带前后空格,务必 .trim();变量名必须带 -- 前缀,写成 'brand-color' 会静默失败。
第五步:处理数值参与计算的情况
这一步要做的,是让变量能安全地参与 calc(),得到按比例缩放的效果。长度型变量直接算就行,但数量型(无单位数字)变量需要配合 calc() 和乘法。
:root {
--gap: 16px;
--scale: 1.5;
}
.box {
padding: calc(var(--gap) * 2);
font-size: calc(16px * var(--scale));
}
想让 --scale 参与 font-size 这种需要单位的场景,必须把单位留在带单位的那一侧(如上例的 16px),不要把单位写进变量再拼字符串。
需要限制类型、设默认值或做类型校验时,可以用 @property 注册:
@property --angle {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
注册后才能对 --angle 做过渡动画,未注册的变量不能直接 transition。
注意:@property 属于较新的特性,旧版浏览器不支持,做兼容方案时不要把它当成唯一手段。
第六步:按用途分组,别把所有东西都塞进变量
这一步要做的,是把变量按层级整理,得到一份好维护的变量表。常见做法分两层:基础色板(--blue-500)和语义变量(--brand-color: var(--blue-500))。改主题只动语义层,业务代码只引语义层。
:root {
--blue-500: #2f6fed;
--brand-color: var(--blue-500);
--text-secondary: #6b7280;
}
注意:不要把写死只出现一次的属性值也抽成变量,变量越多,改样式时越难判断它到底来自哪里。只抽“重复出现”和“需要主题切换”的部分。
小结
- 定义用
--name: value;,放 :root 就是全局,写在某个容器上就是局部。
- 使用用
var(--name, 回退值),回退值可嵌套变量。
- 变量会继承,靠“父级重声明同名变量”就能实现主题切换和局部换装。
- JS 用
setProperty 写、getPropertyValue 读(记得 .trim())。
var() 不能用于媒体查询条件和属性名;参与 calc() 时注意单位留在有单位的一侧。
- 变量分两层:基础色板 + 语义变量,只抽重复和需切换的值。