
本文翻译自:
https://dev.to/afif/what-no-one-told-you-about-css-variables-553o
CSS变量很棒,但是你真的了解有关CSS变量的所有信息吗?
在这篇文章中,我将重点介绍一些关于CSS变量的怪异之处。希望看完这篇文章,你会对它们大为改观。
CSS变量的怪异之处,有:
小心使用 !important无法存储网址 可以使无效值有效 可以不带单位 可以使用动画 无法存储 inherit
值可为空 CSS变量不是C++变量 只从父级到子级起作用 可以使用奇怪的语法
1)小心使用 !important
在CSS变量中使用!important
有点棘手,所以让我们先从一个基础示例开始:
p {
--color:red!important;
color:var(--color);
color:blue;
}
p
的颜色是什么?你认为它为red
,是因为有以下代码:
p {
color:red!important;
color:blue;
}
回答错误!p
的颜色是蓝色的,因为:
p {
color:red;
color:blue;
}
在这种情况下,!important
不是color值的一部分,而是用于增加--color
的特异性。下面这段解释摘自技术规范:
注意:CSS变量(自定义属性)末尾可以包含
!important
,但是CSS解析器会自动将其从属性的值中删除,并使CSS级联中的自定义属性变得重要。换句话说,对顶层“!
”字符的禁止不会阻止!important
的使用,因为!important
在进行语法检查之前已被删除。
下面是另一个更易于理解的示例:
p{
--color:red!important;
--color:blue;
color:var(--color);
}
这次的结果就是red
了:
1.我们有两个相同属性--color
,因此我们需要解决级联。因为第一个有!important
,所以它赢了。
2.因为--color:red!important
赢了,所以!important
被删除,然后将值应用于color
。
3.我们有color:red
。
让我们将代码修改成如下:
p{
--color:red!important;
--color:blue;
color:var(--color);
color:blue;
}
按照相同的逻辑,我们先为--color
和color
解决优先级。--color:red!important
赢了,color:blue
也是如此,所以最后我们得到blue
,因为我们不再关注color:var(--color)
了。
一个重要的规则是始终将CSS变量(自定义属性)视为普通属性,而不仅仅是存储值的变量。
自定义属性是普通属性,因此可以在任何元素上声明它们,使用常规继承和级联规则进行解析,可以使用
@media
和其他条件规则进行条件设置,可以在HTML的style
属性中使用,可以读取或设置使用CSSOM等。
2)无法存储网址
这可能是你经常会遇到的限制。
错误做法 ❌
:root {
--url:"https://picsum.photos/id/1/200/300";
}
.box {
background:url(var(--url));
}
正确做法 ✔️
:root {
--url:url("https://picsum.photos/id/1/200/300");
}
.box {
background:var(--url);
}
此限制与url()
的解析方式有关。解释起来有些麻烦,但正如我们看到的那样,要修复非常容易。只要在CSS变量中添加url()
部分即可。
3)可以使无效值有效!
这是我最喜欢的一点,也是很让人头痛的地方。
还是从一个基础的例子开始:
.box {
background: red;
background: linaer-gradient(red, blue);
}
我们的.box
将有渐变色...不对,实际上是只有red背景。啊呀!linear-*
中打错字了。我之所以这么快注意到我的错误,是因为浏览器跳过了此声明,使用了前一个声明。

现在,让我们介绍一个变量:
.box {
--color:red;
background: var(--color);
background: linaer-gradient(var(--color), blue);
}
测试代码后你会发现,背景现在是透明的,并且我们的第二个声明不再跳过,因为它现在是有效的了。你甚至会发现第一个声明才是跳过的那个,因为第二个声明将其覆盖了。
这到底是怎么回事??明明第二个属性中linaer-gradient
拼写错误了。
当在属性中使用变量时,浏览器将仅在“计算值时间”中计算此属性的值,因为我们首先需要了解变量的内容。在这种情况下,浏览器在进行级联时将认为该值有效,只有到后来它才会变得无效。
在我们的例子中,浏览器在解决级联之后考虑最后一个声明。然后,在进行计算时,它似乎无效的,因此将被忽略。因为已经解决了级联,并且最后不是透明的背景,所以我们将不再返回上一个声明。
你可能会认为这种行为是不合逻辑的,但实际上是合乎逻辑的,因为基于CSS变量,值可以是有效也可以是无效的,因此浏览器刚开始是不知道的。
.box {
--color:10px; /* a "valid" variable */
background: red; /* a "valid" declaration */
background:linear-gradient(var(--color),blue); /* a "valid" declaration that will override the first one */
/* The result is an "invalid" value ... */
}
如果声明包含以初始值引用自定义属性的
var()
,则声明在计算值时可以是无效的,如上所述,或者声明使用有效的自定义属性,但是在替换了var()
函数之后,属性值无效。发生这种情况时,属性的计算值是属性的继承值还是初始值取决于该属性是否被继承,就好像该属性的值已被指定为unset
关键字一样。
用简单的话来说:CSS变量将在备用模式下使用属性的状态,直到我们进行计算为止。只有在计算之后,我们才能说它是有效还是无效的。如果无效,那就太迟了,我们不能再回过头去使用另一个了。
4)可以不带单位
几乎所有的教程/课程都会展示类似这样的例子:
:root {
--p: 10px;
}
.box {
padding: var(--p);
}
但是你也可以执行如下操作:
:root {
--p: 10;
}
.box {
padding: calc(var(--p)*1px);
}
在变量中提供单位不是强制性的,在某些情况下,最好使用无单位值,因为添加单位相当容易,而我们可能需要对不同的单位使用相同的值。
举一个例子

HTML:
<div class="box">
<div class="content">
CONTENT<br> Some more Content
</div>
</div>
<div class="box" style="--b:3;--c:10">
<div class="content">
CONTENT<br> more Content
</div>
</div>
<div class="box" style="--b:1;--c:40">
<div class="content">
AA BB
</div>
</div>
<div class="box" style="--b:1;--c:5">
<div class="content">
AA BB
</div>
</div>
CSS:
.box {
--b:2; /* border length (without unit!!)*/
--c:25; /* the cube perspective (without unit!!)*/
--g:calc((var(--c) - var(--b))*0.707px); /* 0.707 = cos(45deg) = sin(45deg) */
margin: 10px;
display: inline-block;
border-style:solid;
border-width:calc(var(--b)*1px) calc(var(--b)*1px) calc(var(--c)*1px) calc(var(--c)*1px);
border-image-slice:var(--b) var(--b) var(--c) var(--c);
border-image-source:linear-gradient(-45deg,transparent var(--g),red 0 calc(100% - var(--g)),transparent 0);
}
.content {
padding: 5px;
font-size:40px;
}
千万不要忘记这个重要功能。或许有一天它会成为你的救星。
5)可以使用动画
最初,按照规范,CSS变量被定义为非动画属性:
Animatable: no
但是事情发生了变化。得亏于新的@property
,我们可以使用CSS变量进行动画/过渡。
但浏览器支持率仍然很低(尤其是Firefox),但是现在我们应该要了解这个知识点了。
6)不能存储inherit值
请看以下示例:
<div class="box">
<div class="item"></div>
</div>
.box {
border:2px solid red;
}
.item {
--b:inherit;
border:var(--b);
}
凭直觉,我们可能认为.item
将继承其父元素的相同边框,因为--b
包含inherit
,但其实不然(你可以试试看)。
正如我在(1)中所解释的那样,常见的错误是认为CSS变量将仅存储我们以后可以使用的值。CSS变量(自定义属性)是普通属性,因此可以应用inherit
,但不能存储。
例子:
.box {
--b:5px solid blue; /* we define the variable on the parent */
}
.item {
--b:inherit; /* the child will inherit the same value so "5px solid blue"*/
border:var(--b); /* we will have "5px solid blue" */
}
如你所见,继承逻辑的应用方式与普通属性相同。
值得注意的是,上述操作是无用的,因为默认情况下CSS变量是继承的。就像将inherit
设置为默认情况下被继承的属性(例如color)一样。
顺便说一句,其他关键字,例如unset
和revert
,适用相同的逻辑。
7)可以为空
是的,你可以执行以下操作:
.box {
--color: ;
background:var(--color);
}
根据技术规范,上述代码有效:
注意:虽然
<declaration-value>
必须代表至少一个令牌,但是令牌可以是空格。这意味着--foo: ;
是有效的,并且相应的var(--foo)
调用将使用单个空格作为其替换值,但--foo:;
是无效的。
注意最后一句话,原因是因为我们需要至少有一个空格。以下是无效的:
.box {
--color:;
background:var(--color);
}

此怪异之处主要与fallback功能结合使用来做一些魔术。
用一个基本的例子来理解这个技巧:
.box {
background:
linear-gradient(blue,transparent)
var(--color,red);
}
<div class="box">
I will have `background:linear-gradient(blue,transparent) red;`
</div>
<div class="box" style="--color:green">
I will have `background:linear-gradient(blue,transparent) green;`
</div>
<div class="box" style="--color: ;">
I will have `background:linear-gradient(blue,transparent) ;`
</div>
1.第一个box没有定义变量,因此将使用后备。
2.第二个box定义了一个变量,因此使用这个。
3.最后一个定义了一个空变量,因此使用了空值。就像我们不再有var(--color,red)
那样。
空值使我们可以从属性中删除var()
声明!在复杂值内使用var()
时,这很有用。
如果单独使用var()
,则适用相同的逻辑,但最终将得到一个空值,该值对于大多数属性都是无效的。
如果我们以第一个例子为例,我们有background: ;
,这会在“计算值时”导致成为无效值(看第(3)点),因此background是透明的。
8)CSS变量不是C++变量
不幸的是,许多开发人员往往将CSS变量与其他语言的变量进行比较,然后在逻辑上遇到很多问题。对于此特殊原因,我就不将它们称为变量,而将其称为“自定义”属性,因为它们是属性。
大家想这样做:
:root {
--p:5px;
--p:calc(var(--p) + 1px); /* let's increment by 1px */
}
:root {
--x: 5px;
--y: 10px;
/* let's do a variable switch */
--c: var(--y);
--y: var(--x);
--x: var(--c);
}
.box {
--s: 10px;
margin:var(--s); /* I want 10px of margin */
--s: 20px;
padding:var(--s): /* then 20px of padding */
}
以上所有内容将永远无法起作用。前两个无效,因为有一个循环依赖性:变量引用了自身(第一个示例),或者一组变量创建了循环(第二个示例)。
在最后一个示例中,padding
和margin
都将为20px,因为级联会将最后一个声明--s: 20px
设置为高优先级,这将同时应用于margin
和padding
。
也就是说,在使用CSS变量时,你应该不考虑C++,Javascript,Java等,因为它们是有自己逻辑的自定义属性。
9)只从父级到子级起作用。
记住这条黄金法则:CSS变量总是从父元素(或祖先)行进到子元素。它们从不从子代到父代或兄弟元素之间行进。
这会导致我们出现以下错误:
:root {
--c1: red;
--c2: blue;
--grad: linear-gradient(var(--c1),var(--c2);
}
.box {
--c1: green;
background:var(--grad);
}
你认为.box
的背景是linear-gradient(green, blue)
?不,它是linear-gradient(red, blue)
。
根元素是DOM中最上层的元素,因此它是box元素的祖先,黄金法则说明只能是父->子,因此--c1
不能朝相反的方向到达根元素,改变--grad
,然后回到另一个方向重新发送更改值--grad
。
在这个示例中,.box
将继承用root
内部的--c1
和--c2
值定义的--grad
值。更改--c1
只会更改.box
中--c1
的值,仅此而已。
10)可以使用奇怪的语法
最后一个有趣的奇异之处。
你知道你可以执行以下操作吗?
body {
--:red;
background:var(--);
}
太赞了!是的,只用两个破折号就能来定义CSS变量。
你觉得上面的代码很疯狂,那么请看以下内容:
body {
--📕:red;
--📗:green;
--📘:blue;
--📙:orange;
}
是的,有了表情符号!你可以使用表情符号定义变量,并且可以正常工作。
CSS变量的语法几乎怎么写都可以,唯一的要求就是以--
开头。你也可以以数字开头(例如:--1
:)。
为什么不只是破折号或相同的变量存储两个不同的值呢:
body {
---------:red;
background:var(---------);
}
还是存储两个不同值的同一个变量:
body {
--:red;
--:blue;
background:linear-gradient(90deg, var(--),var(--));
}
试一下,你会发现得到的是渐变色!
当然,除非你想让同事发疯,否则永远不要在真实的项目中这样做。
总结
感谢大家的阅读,希望能对大家有所帮助。
(文本完)

每日分享前端插件干货,欢迎关注!
点赞和在看就是最大的支持❤️




