What no one told you about CSS Variables

2025-05-24

关于 CSS 变量,没人告诉你的事

CSS 变量很棒,但是您对它们了解一切吗?

在这篇文章中,我将重点介绍一些鲜为人知的 CSS 变量怪癖。读完之后,你对它们的看法将焕然一新。


目录

  1. 小心!important
  2. 他们无法存储 URL
  3. 他们可以使无效值有效
  4. 它们可以无单位使用
  5. 它们可以动画化
  6. 它们无法存储inherit价值
  7. 它们可以是空的
  8. CSS 变量不是 C++ 变量
  9. 他们只在父母和孩子之间工作
  10. 它们可能有奇怪的语法

1)小心!important

使用!importantCSS 变量有点棘手,所以让我们从一个简单的例子开始:

p {
  --color:red!important;

  color:var(--color);
  color:blue;
}
Enter fullscreen mode Exit fullscreen mode

的颜色会是什么p?你认为这是red因为我们会有以下内容:

p {
  color:red!important;
  color:blue;
}
Enter fullscreen mode Exit fullscreen mode

但事实并非如此!的颜色p将是蓝色,因为我们将具有以下内容:

p {
  color:red;
  color:blue;
}
Enter fullscreen mode Exit fullscreen mode

!important在这种情况下, 不是 color 值的一部分,而是用来增加 的特殊性--color。从规范中可以看出:

注意:自定义属性可以包含尾随的 !important,但 CSS 解析器会自动将其从属性值中移除,从而使自定义属性在 CSS 级联中成为“important”。换句话说,禁止使用顶级“!”字符并不能阻止使用 !important,因为 !important 在语法检查之前就被移除了。

为了更好地理解,这里有另一个例子:

p{
  --color:red!important;
  --color:blue; 

  color:var(--color);
}
Enter fullscreen mode Exit fullscreen mode

上面会给我们一种red颜色:

  1. 我们有两个相同属性的声明,--color因此我们需要解决级联问题。第一个声明为 having !important,因此它胜出
  2. 我们有获胜者(--color:red!important),因此!important被移除,然后将值应用于color
  3. 我们有color:red

让我们编写代码:

p{
  --color:red!important;
  --color:blue; 

  color:var(--color);
  color:blue;
}
Enter fullscreen mode Exit fullscreen mode

--color按照同样的逻辑,我们解决了和的级联color--color:red!important是赢家,并且的也是赢家,color:blue所以最后我们得到了,blue因为我们不再关心了color:var(--color)

一个重要的规则是始终将 CSS 变量(自定义属性)视为普通属性,而不仅仅是存储值的变量。

自定义属性是普通属性,因此可以在任何元素上声明,使用常规继承和级联规则进行解析,可以使用@media和其他条件规则进行条件设置,可以在HTML的style属性中使用,可以使用CSSOM进行读取或设置等。参考


2)它们无法存储 URL

这是一个你有一天会遇到的常见限制。

你不能做什么❌

:root {
  --url:"https://picsum.photos/id/1/200/300";
}
.box {
  background:url(var(--url));
} 
Enter fullscreen mode Exit fullscreen mode

你应该做什么✔️

:root {
  --url:url("https://picsum.photos/id/1/200/300");
}
.box {
  background:var(--url);
} 
Enter fullscreen mode Exit fullscreen mode

此限制与解析方式有关url()。解释起来有点棘手,但正如我们所见,修复起来相当简单。始终url()在 CSS 变量中添加该部分。

如果您想要更准确的细节,我建议您阅读这个Stack Overflow 答案


3)它们可以使无效值变得有效!

这是我最喜欢的怪癖,它会让你很头疼。

让我们从一个基本示例开始:

.box {
  background: red;
  background: linaer-gradient(red, blue);
}
Enter fullscreen mode Exit fullscreen mode

我们的.box将具有渐变色……等等,不,它有red背景。啊!我在 中打错了linear-*。我很容易注意到我的错误,因为浏览器跳过了声明并使用了前一个。

替代文本

现在,我们引入一个变量:

.box {
  --color:red;
  background: var(--color);
  background: linaer-gradient(var(--color), blue);
}
Enter fullscreen mode Exit fullscreen mode

测试代码,你会发现背景现在是透明的,第二个声明不再被划掉,因为它现在是有效的了。你甚至会注意到第一个声明被划掉了,因为第二个声明覆盖了它。

这里到底发生了什么事?!!

当在属性中使用变量时,浏览器只会在“计算值时”评估该属性的值,因为我们需要首先知道变量的内容。在这种情况下,浏览器在执行级联时会将该值视为有效,只有在之后才会变为无效

在我们的例子中,浏览器在解析级联后会考虑最后一个声明。然后在执行评估时,它似乎无效,因此会被忽略。由于我们已经解析了级联,因此我们不会回到上一个声明,最终结果是没有背景,因此背景是透明的。

您可能认为这种行为不合逻辑,但它确实合乎逻辑,因为基于 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 ... */ 
}

Enter fullscreen mode Exit fullscreen mode

如果属性包含一个或多个 var() 函数,且这些函数在语法上有效,则必须假定整个属性的语法在解析时有效。只有在 var() 函数被替换后,才会在计算值时进行语法检查。ref

如果声明包含一个引用自定义属性及其初始值的 var() 函数(如上所述),或者声明使用了有效的自定义属性,但该属性值在替换其 var() 函数后无效,则该声明在计算值时可能无效。发生这种情况时,属性的计算值要么是其继承值,要么是其初始值,具体取决于该属性是否被继承,就像属性值被指定为 unset 关键字一样。ref

简单来说:CSS 变量会使属性处于待命状态,直到我们进行评估。只有在评估之后,我们才能判断它是有效还是无效。如果无效,那就太晚了,我们无法再使用另一个变量。

相关的Stack Overflow 问题


4)它们可以无单位使用

几乎所有的教程/课程都会向您展示这样的例子:

:root {
 --p: 10px;
}
.box {
  padding: var(--p);
}
Enter fullscreen mode Exit fullscreen mode

但您也可以执行以下操作:

:root {
 --p: 10;
}
.box {
  padding: calc(var(--p)*1px);
}
Enter fullscreen mode Exit fullscreen mode

变量中没有单位不是强制性的,在某些情况下,使用无单位的值甚至更好,因为添加单位相当容易,而且我们可能需要使用具有不同单位的相同值。

这是众多示例中的一个例子(取自这个答案

永远不要忘记这个重要功能。它总有一天会帮你节省时间。


5)它们可以动画化

最初,CSS 变量根据规范被定义为不可动画的属性:

可动画化:否

但情况已经发生了变化,由于新功能,@property我们可以使用 CSS 变量进行动画/过渡。

支持仍然很低(特别是在 Firefox 上),但现在是时候了解这一点了。

以下是我依赖此功能的一些用例:

我会写更多文章来展示我们能用它创造的奇迹。敬请期待!


6)它们无法存储inherit价值

让我们考虑以下示例:

<div class="box">
  <div class="item"></div>
</div>
Enter fullscreen mode Exit fullscreen mode
.box {
  border:2px solid red;
}
.item {
  --b:inherit;
  border:var(--b);
}
Enter fullscreen mode Exit fullscreen mode

直观地,我们可能会认为.item会继承其父元素的相同边框,因为--bcontainsinherit但事实并非如此(你可以尝试看看)。

正如我在 (1) 中解释的那样,常见的错误是认为 CSS 变量只是存储一个我们以后可以使用的值,而不是实际存储的。CSS 变量(自定义属性)是普通属性,因此inherit 适用于 CSS 变量,而不是存储在 CSS 变量中。

例子:

.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" */
}
Enter fullscreen mode Exit fullscreen mode

如您所见,继承的逻辑对它们的应用方式与对常见属性的适用方式相同。

值得注意的是,上述操作毫无意义,因为 CSS 变量默认是继承的。这就像设置inherit一个默认继承的属性一样(color例如)。

这就是说,我已经阐述了一种能够使用 CSS 变量的技术inherit

为了说明这个问题,我们来考虑这个简化的例子:

:root {
  --color:rgba(20,20,20,0.5); /*defined as the default value*/
}

.box {
  width:50px;
  height:50px;
  display:inline-block;
  margin-right:30px;
  border-radius:50%;
  position:relative;
}
.red {background:rgba(255,0,0,0.5);}
.blue {background:rgba(0,255,0,0.5);}

.box:before{
  content:"";
  position:absolute;
  top:0;left:0;right:0;bottom:0;
  border-radius:50%;
  transform:translateX(30px);
  background:var(--color);
  filter:invert(1);
}
<!-- we can
</p>
Enter fullscreen mode Exit fullscreen mode



同样的逻辑也适用于其他关键字,如unsetrevert如何将 CSS 变量设置为未设置的值,“--unset-it: unset”?


7)它们可以为空

是的,您可以执行以下操作:

.box {
  --color: ;
  background:var(--color); 
}
Enter fullscreen mode Exit fullscreen mode

根据规范,以上内容有效

注意:虽然<declaration-value>必须至少表示一个标记,但该标记可以是空格。这意味着--foo: ;是有效的,并且相应的var(--foo)调用将以单个空格作为其替换值,但这--foo:;是无效的。

注意最后一句,因为我们至少需要一个空格。下面的代码是无效的:

.box {
  --color:;
  background:var(--color); 
}
Enter fullscreen mode Exit fullscreen mode

替代文本

这个怪癖主要与后备功能一起使用来做一些神奇的事情。

理解这个技巧的一个基本例子:

.box {
  background:
   linear-gradient(blue,transparent)
   var(--color,red); 
}
Enter fullscreen mode Exit fullscreen mode
<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>
Enter fullscreen mode Exit fullscreen mode
  1. 第一个框没有定义变量,因此将使用后备。
  2. 第二个定义了一个变量,因此它将被使用
  3. 最后一个定义了一个空变量,这样就可以使用空值了。就好像我们不再需要了var(--color,red)

空值允许我们从属性中删除声明!这在复杂值中var()使用时非常有用。var()

如果var()单独使用,则适用相同的逻辑,但最终会得到一个对于大多数属性无效的空值。

如果我们采用第一个例子,我们将得到background: ;在“计算值时间”导致无效值(记住(3))的透明背景。


8)CSS 变量不是 C++ 变量

不幸的是,许多开发人员倾向于将 CSS 变量与其他语言的变量进行比较,最终导致逻辑上出现很多问题。出于这个原因,我不想将它们称为“变量”,而是“自定义属性”,因为它们本身就是属性

每个人都想做的事情

:root {
  --p:5px;
  --p:calc(var(--p) + 1px); /* let's increment by 1px */
}
Enter fullscreen mode Exit fullscreen mode
:root {
  --x: 5px;
  --y: 10px;
  /* let's do a variable switch */
  --c: var(--y);
  --y: var(--x);
  --x: var(--c);
}
Enter fullscreen mode Exit fullscreen mode
.box {
  --s: 10px;
  margin:var(--s); /* I want 10px of margin */
  --s: 20px;
  padding:var(--s): /* then 20px of padding */
}
Enter fullscreen mode Exit fullscreen mode

以上所有方法都行不通。前两个方法根本无效,因为我们有一个循环依赖关系,因为一个变量引用了自身(第一个例子)或一组变量(第二个例子),从而形成了循环。

在最后一个例子中,和都paddingmargin具有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);
}
Enter fullscreen mode Exit fullscreen mode

你认为的背景.box会是 吗linear-gradient(green, blue)?不,会是linear-gradient(red, blue)

根元素是 DOM 中最上层的元素,因此它是我们box元素的祖先,而我们的黄金法则规定我们只能执行父级 --> 子级,因此--c1不能朝相反的方向到达根元素,进行更改--grad,然后我们再回到另一个方向重新发送更改后的值--grad

在这个例子中,.box会继承 的值,该值由inside--grad的值定义。更改只会改变inside的值,仅此而已。--c1--c2:root--c1--c1.box

以下是我围绕这个主题写的更详细的答案:

我正在尝试通过自定义属性缩放尺寸var,这样类就可以组合在一起而不耦合。期望的效果是这 3 个列表将采用三种不同的缩放比例,但正如CodePen 上演示的那样,这 3 个列表都采用相同的缩放比例。我正在寻找……

甚至Stack Overflow 团队也偶然发现了这个怪癖!


10)它们可能有奇怪的语法

最后一个有趣的怪癖。

您知道您可以执行以下操作吗?以下内容不再有效。规范已更新,禁止使用此类内容。

body {
  --:red;
  background:var(--);
}
Enter fullscreen mode Exit fullscreen mode

太神奇了吧?没错,只用两个破折号就能定义 CSS 变量。相关:"--" 是有效的 CSS3 标识符吗?

你认为上面的内容很疯狂,请看一下下面的内容:

body {
 --📕:red;
 --📗:green; 
 --📘:blue;
 --📙:orange;
}
Enter fullscreen mode Exit fullscreen mode

是的,表情符号!您可以使用表情符号定义变量,而且它可以起作用。

CSS 变量的语法几乎允许所有情况,唯一的要求是以 开头--。也可以以数字开头(例如:--1:)。相关:CSS 变量名可以以数字开头吗?

为什么不只使用破折号:

body {
  ---------:red;
  background:var(---------);
}
Enter fullscreen mode Exit fullscreen mode

或者同一个变量存储两个不同的值

body {
  --‎​:red;
  --‎:blue;
  background:linear-gradient(90deg, var(--‎​),var(--‎));
}
Enter fullscreen mode Exit fullscreen mode

尝试上述方法,您将获得渐变色彩!

为了实现这种神奇的效果,我使用了隐藏字符,使得两个变量看起来不同,但实际上它们看起来是一样的。如果你在jsfiddle.net上尝试一下这段代码,你会看到以下内容:

替代文本

当然,你永远不应该在实际项目中使用这样的东西,除非你想让你的老板和同事抓狂。


就是这样

我知道这些信息一下子会很多,但你不必记住所有东西。我尝试将最不为人知和最不直观的行为归类到 CSS 变量周围。如果有一天某些功能没有按预期工作,请回到这里。你很可能在上面找到答案。

最后,我将以一些我回答过的、可能有用的 Stack Overflow 问题作为结尾:

如何在 calc() 表达式中获取 CSS 变量的负值?

如何使用类似于 SASS 的 darken() 的 CSS 变量创建颜色阴影?

如何使用 calc() 在颜色值之间切换?

递归变量可以在css中表达吗?

获取使用类似 calc 的表达式的 CSS 变量的计算值

当单选按钮的选中选择器被触发时,CSS 变量是否可能发生改变?


给我买杯咖啡

或者

成为赞助人

文章来源:https://dev.to/afif/what-no-one-told-you-about-css-variables-553o
PREV
我是如何通过个人项目成为高级 JavaScript 开发者的?一切都可以用几句话概括。我的第一个重大挑战是如何深入前端开发。用我的热情去探索新事物。从项目中触发项目。值得重新发明轮子吗?结论
NEXT
ONE 星级评分系统 — 评分的未来 <one-star-rating> Web 组件