文章

CSS变量与自定义属性深入解析

CSS 自定义属性的定义/继承/备用值、与 Sass 变量的本质区别、主题切换与运行时动态样式的实战,面试常考。

CSS变量与自定义属性深入解析

一句话概括

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、组件定制全靠它撑起来。

本文由作者按照 CC BY 4.0 进行授权