紫粉色渐变上两个交叠的半透明圆角方块

CSS 变量为什么不生效

调 CSS 变量最气人的地方,是它不报错。名字拼错一个字母,浏览器只会安静地把这条声明丢掉,你看到的就是「样式没生效」。

下面这几种,我几乎都踩过。

一、名字拼错了,而且大小写敏感

:root { --brand-color: #b4552d; }
.title { color: var(--brand-colour); }  /* 多了个 u */

--brand-color 和 --brand-colour 是两个变量。拼错不会报错,var() 拿不到值就直接失效。

排查办法:在控制台里看一眼到底有没有定义:

getComputedStyle(document.documentElement).getPropertyValue('--brand-color')

有值说明定义没问题,问题在引用;返回空字符串,就是压根没定义上。

二、作用域不对,子元素拿不到

变量是继承的,不是全局的。

.card { --pad: 20px; }
.card p { padding: var(--pad); }   /* 能拿到 */
.other { padding: var(--pad); }    /* 拿不到 */

--pad 只定义在 .card 上,只有 .card 和它的后代能继承。想全局用就挂到 :root 或者 html。

反过来也容易翻车:在组件里重新定义了同名变量,结果把外层的覆盖掉了。

三、定义在 :root 之外,又被更高优先级覆盖

:root { --fg: #222; }
.dark { --fg: #eee; }

如果 .dark 加在 html 上,它和 :root 是同一个元素,按优先级和顺序比较;如果加在 body 上,就是继承关系——body 里的元素用 body 的值,html 自己的 color 还是旧的。

判断方法:在元素上直接看计算值,别猜。

四、把 var() 塞进简写属性,整条声明被丢弃

这条最隐蔽:

.box {
  --ease: cubic-bezier(.22,1,.36,1);
  transition: transform .5s var(--ease);   /* 某些情况下整条失效 */
}

规范上这样写是允许的,但只要变量解析出来的值不合法(比如多了一个逗号、少了单位),整条声明就会被判无效,而不是只忽略那一部分。表现就是「动画完全没了」。

稳妥写法是拆成长写:

transition-property: transform;
transition-duration: .5s;
transition-timing-function: var(--ease);

五、var() 的兜底值写法不对

color: var(--fg, #333);      /* 对:拿不到就用 #333 */
color: var(--fg #333);       /* 错:少了逗号,整条无效 */

兜底值里如果本身带逗号,要再包一层:var(--x, rgb(0 0 0 / .5)) 没问题,但 var(--x, a, b) 会把 a, b 都当成兜底值。

六、JS 设到了错的元素上

document.body.style.setProperty('--atmo', 'dark');

如果 CSS 里是 html[data-atmo="dark"] 这种选择器,设 --atmo 根本没用——它读的是属性,不是变量。

还有一种是设了变量但没触发重绘(极少数情况,通常是设在了不参与渲染的节点上)。

七、伪元素里拿不到

.card::before { background: var(--accent); }

伪元素能继承宿主元素的变量,但如果变量定义在 ::before 自己身上,或者定义在一个不相关的作用域,同样拿不到。

一个万能排查顺序

  1. getComputedStyle(el).getPropertyValue('--名字') —— 先确认有没有值。
  2. 在 DevTools 的 Styles 面板里看那条 var() 是不是被划掉了(划掉 = 声明无效)。
  3. 顺着 DOM 往上找,看变量定义在哪个祖先上。
  4. 检查是不是被同名的、优先级更高的规则覆盖了。

CSS 变量最好用的地方,是能一处改、全站变;最容易坑人的地方,也是它错得毫无声响。所以我现在写变量,名字一律小写加连字符,定义只放 :root 和组件根节点,var() 里永远带一个兜底值。