本文是译文,采用意译。

​CSS​​ 变量很不错哦,但是你知道它们的详情?

1. 小心 !important

与 ​​CSS​​​ 变量一起使用 ​​!important​​ 有点诡异,如下面的示范:

p {
--color:red !important;

color:var(--color);
color:blue;
}

上面的 ​​p​​​ 元素会是什么颜色呢?你会认为是 ​​red​​ 红色吧,认为按如下代码执行:

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

但是,并不是 ​​red​​ 红色,因为实际执行是这样:

p {
color: red;
color: blue;
}

在这种情况下,​​!important​​​ 并不是 ​​color​​​ 的一部分,而是增加了 ​​--color​​​ 变量的特性。规范中指明:

注意:自定义属性可以包含 ​​!important​​​ ,但是会被 ​​CSS​​ 解析器自动从属性中删除,这将自定义的属性 important 变成层级。换言之, ​​!important​​ 并不是不会起作用,而是在语法检查之前就被忽略了。

下面这个例子你会更容易明白:

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

color: var(--color);
}

上面的代码会赋予 ​​p​​ 元素红色,解析如下:

  1. 我们对​​--color​​ 属性有两次的声明,所以我们需要解决其层级的问题。第一次的定义带有​​!important​​ ,所以它的层级相对高
  2. 接着​​var(--color)​​​ 将会应用​​red !important​
  3. 所以我们会得到​​color: red​

再来看一段代码:

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

color:var(--color);
color: pink; <!-- 这里我改写的颜色值 -->
}

按照上面的逻辑,我们最后会得到粉红色 ​​pink​​ 的段落颜色。

一个很重要的规则是应当将 CSS 变量(自定义属性)看作普通属性,而不仅仅是存储值的变量

自定义属性是普通属性,所以它们可以被定义在任何的元素上,可以使用普通属性的继承和联级规则解决,可以使用 ​​@media​​​ 和其他条件规则进行条件处理,可以用于 ​​HTML​​​ 的 ​​style​​​ 属性,可使用 ​​CSSDOM​​ 读取和设置等等。

2. 它们不可以存储 urls

总有一天你会偶然发现这个常见的限制。

你不能这样做 ❌

: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()​​​ 是怎么解析的。解析起来有点棘手,推荐你到 Stack Overflow Answer​ 上找答案。正如你所见,我们修复起来很容易,把 ​​url()​​ 整个赋予变量即可。

3. 它们可以无效的值变有效

这也是我喜欢的点之一,也是让人头疼的点。

我们以一个基础的案例入手:

.box {
background: red;
background: linaer-gradient(red, blue);
}

​.box​​​ 元素将会有一个红蓝渐变的效果,对吧?然而却是纯红色。嗯,我打错了 ​​linear-*​​。我可以很容易发现这个错误,因为浏览器划掉了这一行并启用了上一行的背景样式。

没人告诉你关于 CSS 变量的那些事_代码规范

现在,让我们来介绍变量:

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

测试这段代码,你会发现背景颜色变成了透明。我们的第二个背景颜色并没有被浏览器划掉,反而是第一个背景样式被划掉了。因为第二个背景样式重写了第一个。

没人告诉你关于 CSS 变量的那些事_css_02

为什么会发生这样的事情 ?

当我们使用变量作为属性,浏览器只会在 "计算值时间" 去评估值,因为我们需要首先知道变量的内容。在这种例子中,当浏览器做联级时,会认为属性值是有效的,之后才会变成无效。

在我们的例子中,浏览器做级联时,认为最后一个声明是有效的。但是到评估值的时候,最后一个声明被认定是无效的,所以它被忽略。我们不会回头查看,因为我们在级联的时候已经处理过了,所以我们最后会得到一个透明的背景颜色。

你可能认为这种行为不符合逻辑的,但是它确符合逻辑。因为一个值是有效还是无效时基于 ​​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()​​ 函数被替代后,在“计算值时间”才做语法检查。

简单来说:​​CSS​​ 变量将属性的状态作为一个后备,知道我们对其进行评估值。当评估值之后,我们可以说它是有效的或者无效的了。如果它是无效的,那么久太晚了,因为我们不会再回头使用上一个。

4. 它们可以不被使用单位

大多数的教材或课程都会展示下面的代码给你:

:root {
--p: 10px;
}
.box {
padding: var(--p);
}

但是,你也可以这么做:

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

变量中拥有单位并不是强制的。

5. 他们可以动起来

最初,​​CSS​​ 变量被定义是没有动画属性的。

Animatable: no

但是,事情发生了变化,我们通过 ​​@property​​​ 修饰,​​CSS​​ 变量可以做一些动画或者渐变。这个特性目前浏览器支持比较低,但是也是时候了解下了。

6. 它们不可以存储 ​​inherit​​ 值

我们考虑下面的例子:

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

直觉告诉我们,​​.item​​​ 将会即成父元素的 ​​border​​​,因为 ​​--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" */

正如你所看到的,公共属性应用,逻辑上才可以继承 ​​inherit​​。

上面的写法是鸡肋的,因为 ​​CSS​​ 变量默认是继承的。

7. 它们可以是空值

是的,你可以想下面这么做:

.box {
--color: ;
background:var(--color);
}

笔记:​​<declatation-value>​​​ 声明值必须代表一个标记,所以变量空值需要有一个空格。比如 ​​--foo: ;​​​ 是有效的,​​var(--foo)​​​ 将会返回一个空格。​​--foo:;​​ 是无效的。如下:

.box {
--color:; ❌
background:var(--color);
}

没人告诉你关于 CSS 变量的那些事_代码规范_03

这个小技巧可以配合预设特性实现一些想不到的效果。

一个例子你就会明白这个技巧:

.box {
background:
linear-gradient(blue,transparent)
var(--color,red); # 没有发现--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. 第二个有定义的变量,所以它被使用
  3. 最后一个设定了一个空的变量值,所以空值被使用。使用后就好比不需要​​var(--color, red)​​ 一样

空值允许我们移除属性中 ​​var()​​​ 声明,在一个复杂的值中使用 ​​var()​​ 作用挺大。

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​​ 的变量去应用。

这很不幸,你应该停止用 ​​C++, Javascript, Java​​​ 等语言的思维去思考 ​​CSS​​ 变量,因为它们有自己逻辑的自定义属性。

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)​​。

​root​​​ 元素是 ​​DOM​​​ 元素的最上层,所以它是 ​​box​​​ 的祖先元素,我们应用上面的黄金规则知道,子元素的 ​​--c1​​​ 是跑不到 ​​linear-gradient(var(--c1),var(--c2)​​ 中的。

10. 它们可以有奇怪的语法

最后一个也是有趣的一个。

你知道你能像下面这样写么?

body {
--:red;
background:var(--);
}

很神奇,对吧?是的,​​CSS​​ 变量可以仅使用两节虚线定义。

你会认为上面已经很疯狂了?看下下面这个:

body

是的,你还可以用表情来定义一个变量。

​CSS​​​ 变量允许你以 ​​--​​ 开头的任何内容。比如:

body {
---------:red;
background:var(---------);
}

又比如:

body {
--‎:red;
--‎:blue;
background:linear-gradient(90deg, var(--‎),var(--‎));
}

其实上面是这样的:

没人告诉你关于 CSS 变量的那些事_背景颜色_04

当然你不会使用这么稀奇古怪的东西在你的实际项目中,除非你想让你的老板或合作开发者发疯 😜

总结

不知不觉有很多的内容了,你不需要马上就记住所有的内容。我尽力将这些不为人知的 ​​CSS​​ 变量整理了出来。如果有一天你工作中需要这些知识点,你可以回头来看。

原文:What no one told you about CSS Variables