CSS变量,官方名称叫自定义属性(Custom Properties),是CSS规范中一个非常实用的特性。它的写法是以两个短横线开头,比如--main-color: #3498db,然后通过var()函数在任意属性中引用这个值。相比Sass、Less这类预处理器变量,CSS变量的最大优势在于它是浏览器原生支持的、运行时动态可变的,可以在页面加载后由JavaScript随意修改,也能跟随DOM继承链自动传递。这篇文章会把var()的语法细节、作用域规则和实际应用场景讲清楚。

一、CSS变量的声明与var()的基本语法
声明一个CSS变量非常简单,任何选择器的声明块里都可以定义,但最常用的做法是定义在:root伪类上,这样变量会挂载到文档根元素,全局可用:
:root {
--main-color: #3498db;
--spacing: 16px;
--font-size: 14px;
}
.button {
color: var(--main-color);
padding: var(--spacing);
font-size: var(--font-size);
}var()函数接收两个参数:第一个参数是变量名,必须以--开头;第二个参数是可选的默认值,当变量未定义时生效。例如color: var(--main-color, #333),如果--main-color不存在,浏览器就会回退使用#333。需要注意这个默认值只在变量「未定义」时才生效,如果变量定义了但值为空串,浏览器会把这个属性当成非法值处理,默认值并不会兜底。
第二个参数还可以嵌套另一个var(),实现多级回退:比如var(--theme-color, var(--fallback-color, black))。当第一个变量缺失时尝试第二个,再缺失就用最终的黑色。这种写法在开发可复用组件库时特别有用,组件内部定义一套兜底变量,外部可以按需覆盖。
还有一个容易忽略的点:变量名是区分大小写的。--Main-Color和--main-color是两个完全不同的变量,混用会导致取不到值。另外变量值可以是任何合法的CSS值片段,包括颜色、长度、甚至一段完整的属性值组合,比如--shadow: 0 2px 4px rgba(0,0,0,.2),然后直接box-shadow: var(--shadow)。
二、作用域与继承规则:变量是怎么传递的
CSS变量遵循标准的CSS层叠和继承规则。定义在:root上的变量作用域是全局的,而定义在某个具体元素上的变量,只在该元素及其后代元素中可用。看下面这个例子:
:root {
--text-color: black;
}
.card {
--text-color: #666;
}
.card .title {
color: var(--text-color); /* 取到 #666,继承自最近的 .card */
}
.other {
color: var(--text-color); /* 取到 black,来自 :root */
}这种「就近覆盖」的机制让CSS变量天然适合做主题定制。父级设置一套变量值,所有子组件统一引用,换一套变量值就等于换了整套皮肤。而且变量在计算之前就会被解析到引用处,也就是说浏览器会在实际使用变量的那个元素上,沿继承链向上查找最近的定义值。
媒体查询中重定义变量也是常见手法,可以让整个布局随视口变化而无需重复写多套属性:
:root {
--container-width: 1200px;
}
@media (max-width: 768px) {
:root {
--container-width: 100%;
}
}
.container {
width: var(--container-width);
}需要提醒的是,变量引用不能形成循环依赖。如果--a: var(--b)而--b: var(--a),浏览器会把这两个变量都判定为无效,所有引用它们的地方都会变成属性的初始值或继承值。此外,在简写属性中使用var()时要格外小心,比如font: var(--font-style) 16px/1.5 sans-serif,一旦变量的值放进去后导致整个声明不合法,整条简写属性都会作废,而不是只作废那一个部分。
三、用JavaScript动态读写CSS变量
这是CSS变量区别于预处理器变量的核心优势。运行时通过getPropertyValue和setProperty就能读写变量:
// 读取 :root 上定义的变量
const root = document.documentElement;
const mainColor = getComputedStyle(root).getPropertyValue('--main-color').trim();
// 修改变量,全局立即生效
root.style.setProperty('--main-color', '#e74c3c');
// 删除变量
root.style.removeProperty('--main-color');典型应用是主题切换。事先定义好亮色和暗色两套变量,切换时只改一个类名或直接改几个变量值,页面所有引用处瞬间更新,不需要遍历DOM逐个改样式,性能开销极小。配合transition还能做出平滑的颜色过渡效果:
body {
--bg: #ffffff;
--text: #333333;
background-color: var(--bg);
color: var(--text);
transition: background-color .3s, color .3s;
}
body.dark {
--bg: #1e1e1e;
--text: #eeeeee;
}
// 切换按钮的点击事件
toggleBtn.addEventListener('click', () => {
document.body.classList.toggle('dark');
});四、实战中的典型应用场景与常见坑
除了主题换肤,CSS变量在组件化开发中也大量使用。比如一个进度条组件,把宽度交给变量控制,父级只需要改一个数字就能调整展示:
.progress {
--percent: 50;
width: calc(var(--percent) * 1%);
background: linear-gradient(
to right,
var(--main-color) calc(var(--percent) * 1%),
#eee 0
);
}
<div class="progress" style="--percent: 80"></div>
直接在元素的内联style里写变量也是合法的,这种「属性即配置」的玩法在很多开源组件库里都能见到。另外配合calc()做数学运算、配合伪元素做参数传递,都是变量发挥价值的地方。
最后总结几个高频踩坑点:第一,var()只能用在属性值的位置,不能用在属性名或选择器里;第二,变量定义时如果值带单位,引用时就不能再拼单位,反之如果只存数字,就要用calc(var(--n) * 1px)的方式补上单位,直接写var(--n)px是无效的;第三,旧版IE浏览器完全不支持,如果项目还需兼容IE11,可以考虑降级方案或继续使用预处理器。掌握这些细节后,CSS变量加var()的组合会成为你样式代码里最趁手的工具之一。