CSS变量与自定义属性深入解析
CSS 自定义属性的定义/继承/备用值、与 Sass 变量的本质区别、主题切换与运行时动态样式的实战,面试常考。
一句话概括
CSS 变量(官方叫 CSS Custom Properties,自定义属性)是 CSS 原生的”变量”能力:用 --name 定义、用 var() 取用,能在任意选择器里复用。它和 Sass/Less 变量最大的不同是”运行时生效”——浏览器实时管理,JS 能改、媒体查询能改、能参与 calc() 计算。
一句话记住:CSS 变量是现代主题切换、设计 token、组件定制的底层基石,也是前端工程化里”少改多处”的关键武器。
核心知识点
1. 基本语法:–name 定义,var() 使用
1
2
3
4
5
6
7
8
9
10
11
:root { /* :root = html,但优先级更高,常用来放全局变量 */
--primary: #42b883;
--radius: 8px;
--gap: 16px;
}
.card {
background: var(--primary);
border-radius: var(--radius);
padding: var(--gap);
}
规则:变量名以 -- 开头、大小写敏感(--Primary 和 --primary 是两个);值可以是任何合法 CSS 值(颜色、长度、甚至整段 background 简写)。
2. 继承与层叠:变量沿着 DOM 树往下传
CSS 变量严格遵循 CSS 的继承和层叠规则——子元素自动继承父元素上定义的变量,且就近覆盖:
1
2
3
4
5
:root { --color: blue; }
.container { --color: red; } /* 覆盖为红,仅作用于自身及后代 */
.container .child { color: var(--color); } /* red(继承 .container) */
.sibling { color: var(--color); } /* blue(继承 :root) */
这比 Sass 变量的”编译期词法作用域”灵活得多:它真的跟着选择器优先级走。
3. var() 的备用值,以及”无效值”陷阱
1
2
3
4
.card { border-color: var(--border, #333); } /* --border 未定义 → 用 #333 */
:root { --bad: 16px; }
.box { color: var(--bad, red); } /* --bad 存在但 16px 对 color 无效 */
⚠️ 关键陷阱:备用值只在”变量根本不存在”时生效。如果变量存在、但值对当前属性无效(如上面 --bad:16px 用在 color 上),浏览器不会用备用值,而是按”无效值”处理——落到 inherit/initial。所以不能指望备用值去兜底”值写错”的情况。
4. CSS 变量 vs 预处理器变量
| 维度 | CSS 变量 | Sass/Less 变量 |
|---|---|---|
| 处理时机 | 运行时(浏览器引擎) | 编译时(构建阶段) |
| 作用域 | 遵循 CSS 选择器继承/层叠 | 遵循代码嵌套作用域 |
| 动态修改 | ✅ JS setProperty 可改 | ❌ 编译后就固定 |
| 媒体查询内改 | ✅ 可以 | ❌ 不行 |
calc() 计算 | ✅ 运行时参与 | ❌ 编译时已替换 |
| 跨文件共享 | ✅ 天然 | ❌ 需 @import |
结论:两者不是替代关系,是互补——Sass 管”构建期 token 生成”,CSS 变量管”运行期动态交互”。
5. calc() + 媒体查询:动态布局
变量能直接进 calc(),这是做响应式栅格、动态间距的利器:
1
2
3
4
5
6
7
8
:root { --cols: 3; --gap: 16px; }
.grid {
display: grid;
grid-template-columns: repeat(var(--cols), 1fr);
gap: var(--gap);
}
@media (max-width: 768px) { :root { --cols: 2; } }
@media (max-width: 480px) { :root { --cols: 1; } }
改一个 --cols,整页栅格列数跟着变——不用写三套 media query 重复样式。
6. 实战:主题切换 + 组件级定制
主题切换是 CSS 变量最经典的用法,一键切换全站颜色:
1
2
3
4
5
6
7
:root { --bg:#fff; --text:#222; --accent:#42b883; }
:root[data-theme="dark"] { --bg:#1a1a2e; --text:#e0e0e0; --accent:#e94560; }
body { background: var(--bg); color: var(--text); transition: background .3s, color .3s; }
/* JS 一行切换:document.documentElement.setAttribute('data-theme','dark') */
/* 还能跟随系统:@media (prefers-color-scheme: dark) { :root { ... } } */
组件库常用变量”暴露定制点”,让使用者不改源码就能换肤:
1
2
.btn { --btn-bg: #42b883; background: var(--btn-bg); }
.danger { --btn-bg: #e74c3c; } /* 在父容器覆盖即可 */
其实你每天都在用
- 明/暗主题切换按钮:点一下改
data-theme或setProperty,全站颜色跟着变 - 设计系统的颜色 token:
--color-primary全局复用,品牌色改一处全站生效 - 统一间距/圆角变量:
--gap、--radius集中管理,风格不再散落各处 - 组件库换色:antd/Element 的
--el-color-primary这类,就是 CSS 变量 - 字号随系统缩放:
font-size: calc(16px + var(--scale) * 1px)由 JS 控制--scale - 栅格列数随屏宽变:
grid-template-columns: repeat(var(--cols), 1fr) - 动画距离动态控制:
transform: translateX(var(--distance)),JS 设--distance
常见误解(FAQ)
❌ 误区一:”CSS 变量和 Sass 变量差不多,随便用”
本质不同:Sass 变量在编译期被替换成固定值,之后改不了;CSS 变量在运行期由浏览器管理,JS 和媒体查询都能实时改。需要”运行时动态”(主题切换、按屏宽变、JS 控制)就必须用 CSS 变量。
❌ 误区二:”var 的备用值能兜底任何错误”
只在”变量根本没定义”时兜底。变量存在但值无效(如 color: var(--x) 而 --x: 16px)时,浏览器按无效值处理、落到 inherit/initial,不会用备用值。想要稳妥,得保证变量值本身合法。
❌ 误区三:”CSS 变量只能存颜色”
错。任何合法 CSS 值都能存:长度、数字、百分比、calc() 表达式、多值组合(10px 20px)甚至整段 background。不能存的是选择器或 @规则。
❌ 误区四:”改 CSS 变量性能差,会全页重算”
浏览器对自定义属性做了优化,修改一个变量通常只重算用到它的那些属性,不是整页推倒重来。不过大面积、高频改全局变量仍要谨慎(尤其触发重排的属性),别在 requestAnimationFrame 里疯狂 setProperty。
❌ 误区五:”IE 也支持 CSS 变量”
不支持。IE11 完全不支持自定义属性;要兼容老 IE 只能退化成编译期变量(Sass)或写死值。现代项目(面向 Chrome/Edge/Safari/Firefox)可放心用,必要时配合 @property 给变量注册类型做过渡动画。
一句话总结
CSS 变量就是”–name 定义、var() 取用、沿 DOM 继承、运行时可变“——它把”编译期写死的样式”变成了”运行期能调的参数”,主题切换、设计 token、组件定制全靠它撑起来。