特别声明:如果您喜欢小站的内容,可以点击申请会员进行全站阅读。如果您对付费阅读有任何建议或想法,欢迎发送邮件至: airenliao@gmail.com!或添加QQ:874472854(^_^)
很多Web开发者更喜欢将 *CSS自定义属性 称之为 CSS变量。在2020年中社区中有关于CSS自定义属性的讨论到处可见,小站也有很多关于CSS自定义属性的相关教程,而且在很多Web应用中也可以看到CSS自定义属性的身影。虽然如此,但有很多开发者对CSS自定义属性了解的不多,甚至说不怎么理解,也用不好CSS自定义属性。这篇文章和以往介绍CSS自定义属性的文章有所不同,我从不同的角度来阐述CSS自定义属性,主要是希望这篇文章能让Web开发者更好地理解CSS自定义属性,以及如何更好的使用该属性。如果你对该话题感兴趣的话,请继续往下阅读。
CSS自定义属性的发展进程
众所周之,CSS和其他程序语言有一个最大的差异,即 CSS没有变量 这样的概念(也没有逻辑),也正因此,很多Web开发者都觉得CSS是非常简单的,没有技术含量的。
然而,在前端开发者,很多时候希望CSS能像其他程序语言一样,有 变量 的概念,这样利于CSS的编写和维护。比如构建像下图这样的UI Kit:

从上图可以发现,很多UI上都有#0055fe这个色值,也就是说该色值会在CSS样式表中使用多次:
header {
background-color: #0055fe;
}
label {
color: #0055fe;
}
button {
border: 1px solid #0055fe;
}
想象一下,如果你正在维护一个大型的项目,会涉及很多个组件(或者多个.css样式文件),即 #0055fe被分散运用于多个文件中,多个样式块中。突然有一天,你被要求改更颜色(换肤)。那么在没有CSS自定义属性(CSS变量),可能最好的办法就是在整个项目的所有.css文件中查找#0055fe,然后再替换。这么做,是多么痛苦的一件事情,而且还容易被遗漏!
这样的模式可以说是痛苦的,但庆幸的是,在CSS社区中开始使用像Sass、LESS和Stylus等CSS处理器,开发者可以在这些处理器中开始使用变量的概念,比如在Sass中:
$primary-color: #0055fe;
header {
background-color: $primary-color;
}
label {
color: $primary-color;
}
button {
border: 1px solid $primary-color;
}
如此一来,我们可以在一个_var.scss中放置所有样式中会用到的变量,比如$primary-color,然后在需要的地方引用已定义好的变量,目的是 为了实现CSS的值的可重用性和减少冗余。基于该特性,Web开发者可以轻易的实现换肤效果:

另外,CSS这几年发展和变革是非常地快,而且W3C的CSS工作者也知道,CSS也应该具备“变量”这样的特性,为开发者减少重复性的工作和简化工作,并且减少对CSS处理器工具的依赖。为此,2012年左右,W3C CSS小组为CSS加入了 CSS自定义属性(CSS变量) 模块,并在2017年左右获得大部分主流浏览器的支持。
有了CSS自定义属性后,我们可以像下面这样来维护CSS:
:root {
--primary-color: #0055fe
}
header {
background-color: var(--primary-color);
}
label {
color: var(--primary-color);
}
button {
border: 1px solid var(--primary-color);
}
不过CSS原生的自定义属性(变量),它也有一定的缺陷,比如说无法在声明变量的时候指定其语法类型,比如上面示例中,我们只能在:root{}中指定--primary-color自定义属性的值是#0055fe,它只是个字符串,并不是一个<color>。
除了原生的CSS中可以声明自定义属性之外,CSS Houdini的 属性和值API 对CSS自定义属性进行了扩展:

对于CSS Houdini中的自定义属性,我更喜欢称之为 CSS Houdini变量。CSS Hounini变量有两种方式来注册,一种是JavaScript来注册:
CSS.registerProperty({
name: '--primary-color',
syntax: '<color>',
inherits: false,
initialValue: '#0055fe'
})
另外一种是使用@property注册:
@property --primary-color {
syntax: '<color>';
initial-value: #0055fe;
inherits: false;
}
CSS Houdini变量的使用方式和CSS原生的CSS变量使用方式是相同的:
header {
background-color: var(--primary-color);
}
时至今日,你可能还在CSS处理器中使用变量,或许在开始使用原生的CSS变量,也有可能两者混合在一起使用。换句话说,我们有多种方式使用CSS变量,但我们应该根据具体的场景使用更合适合的方式,不过我自己更建议从现在开始就使用原生的CSS变量,因为它有些特性是CSS处理器中变量无法具备的,特别是使用CSS Houdini的变量时,它的特性会变得更强大。在接下来的内容中,有可能会用到CSS Houdini的变量。
CSS自定义属性的基础
上面我们主要介绍了CSS自定义属性(或变量)的发展与变迁,可能对CSS自定义属性了解的还不够深入。如果你从未接触过该方面的特性,可以从CSS自定义属性的一些基础开始,如果你是这方面的专家,你可以选择性的阅读后续的内容。
那我们从CSS自定义属性最基础的开始吧!
CSS自定义属性简述
CSS自定义属性也常被称为CSS变量,被称为CSS变量主要还是源于CSS处理器或其他程序语言的一种叫法。但我想说的是“CSS自定义属性不是变量”。为什么这么说呢?后面会向大家解释。
CSS自定义属性是以--前缀开始命名,比如前面示例中的--primary-color,其中primary-color可以是任何字符串,它也被称为“变量名”。即--变量名(比如--primary-color)组合在一起才是“CSS自定义属性”。
CSS自定义属性的声明和Sass的变量声明有所不同,在Sass中,我们可以在非{}外声明,比如:
$primary-color: #0055fe;
但CSS自定义属性声明需要放置在一个{}花括号内,比如:
:root {
--primary-color: #0055fe;
}
除了在:root中之外,还可以是在其他的代码块中,比如:
html {
--primary-color: #0055fe;
}
header {
--primary-color: #00fe55;
}
虽然按上面的方式在CSS中注册了CSS自定义属性,但如果没有被var()函数引用的话,它们不会有任何效果。比如下面这个示例,只有--primary-color被var()引用,而--gap虽已注册,但未被var()引用,它也就未运用到任何元素上:
:root {
--primary-color: #0055fe;
--gap: 20px;
}
header {
color: var(--primary-color);
}
除了在CSS中使用--varName来注册一个CSS自定义属性之外,我们还可以使用JavaScript的style.setProperty()动态注册一个CSS自定义属性,比如:
document.documentElement.style.setProperty('--primary-color', '#0055fe')
执行完之后,在<html>元素上会添加style属性:
<html style="--primary-color: #0055fe"></html>
在CSS Houdini中,我们还可以使用另外两种方式来注册CSS自定义属性(变量)。在CSS样式文件中可以使用@property注册自定义属性:
@property --primary-color {
syntax: '<color>';
initial-value: #0055fe;
inherits: false;
}
在JavaScript中可以使用CSS.registerProperty()注册:
CSS.registerProperty({
name: '--primary-color',
syntax: '<color>',
inherits: false,
initialValue: '#0055fe'
})
CSS Houdini中注册好的CSS自定义属性同样只有被var()函数调用才能生效。
有一点开发者需要特别注意,CSS中注册的自定义属性是有大小写之分的,比如--on和--ON是两个不同的CSS自定义属性,比如:
:root {
--ON: 1;
}
.box {
transform: rotate(calc(var(--ON) * 45deg));
transition: transform 1s ease-in-out;
}
.box:hover {
transform: rotate(calc(var(--on) * 720deg));
}
.box:last-of-type:hover{
transform: rotate(calc(var(--ON) * 720deg));
}
如果你把鼠标移动蓝色.box上,效果和我们预想的并不相同,没有旋转720deg,反而旋转到了0deg,即--on无效值;如果把鼠标移动到红色的.box上,可以看到元素从45deg旋转到720deg:
从浏览器开发者工具中,我们可以得到,var(--on)(注意,我们在代码中并没有显式声明--on这个自定义属性),那么transform: rotate(calc(var(--on) * 720deg))计算出来的transfrom为none:

那这就引出了第一个问题:当一个var()函数使用一个未定义的变量时,会发生什么?
当一个var()函数使用一个未定义的变量时,会发生什么?
上面的示例告诉我们:var()函数使用一个未定义的变量(自定义属性)并不会导致样式解析错误,也不会阻止样式加载、解析或渲染。这个就好比你在编写CSS时,因为手误将属性或属性值用错一样,客户端只是不识别这个错误的信息,比如:

那么在使用var()中使用一些未定义的CSS变量时,有可能是:
var()函数引用的变量名输错了(手误造成)- 你可能使用
var()引用了一个自认为它存在的CSS变量,但事实上它并不存在 - 你可能正试图使用一个完全有效的CSS变量,但是你想在其他地方使用,它恰好不可见
我们再来看两个示例,先来看一个有关于border的示例:
:root {
--primary-color: #0055fe;
}
body {
color: #f36;
}
.box {
border: 5px solid var(--primay-color);
color: var(--primay-color);
}
你将看到的效果如下:

由于手误,在border和color的var()函数事实上引用了一个并未定义的CSS变量--primay-color(其实是想引用--primary-color)。结果浏览器并不知道border最终的值应该是什么?因为border属性在CSS中是一个不可继承的属性,这个时候浏览器会理解成用户把border属性值写错了。此时,border会被浏览器解析为border: medium none currentColor。
注意,在CSS中,如果
border-style的值被渲染为none时,你是看不到任何边框效果的。
再来看color属性。虽然var()引用的变量也手误写错了,但它却有颜色。这主要是因为color是一个可继承的属性,所以浏览器渲染的时候会继承其祖先元素的color值,在我们这个示例中,在body中显式设置了color: #f36,因此.box的color继承了body的color值,即#f36。
这个示例令人感到困惑的是var()引用错语的变量(其实是不存在的变量),浏览器渲染的时候到底会发生什么?从上面的示例中我们可以得知,它的根源在于var()函数使用了无效的属性,这个时候浏览器渲染CSS时,它自己也无从得知。
浏览器在渲染CSS时,只有属性名或值无法被识别时(浏览器渲染引擎不知道时)才会认为是无效的。但是,var()函数可以解析为任何东西,所以样式引擎不知道var()包含的值是否已知(浏览器渲染引擎可识别)。只有当这个属性真正被使用时,它才会知道,这时,它会默默地回退到属性的继承或初始状态,并让你疑惑发生了什么?当你碰到这个现象的时候,其实可以借助浏览器工发者工具来查找问题:

除此之外,在一些浏览器中还提供了自定义属性和其值的查找,有关于这个部分,我们在介绍浏览器开发者工具的时候会向大家演示。
不知道你有没有发现,CSS原生中的自定义属性是一个字符串(前面有提到过),可以说并不很严谨。比如说,--primary-color应该是一个颜色值(<color>),但有的时候在另外一个地方再次注册的时候,它可能被开发者定义成一个长度值(<length>),比如:
:root {
--primary-color: #0055fe;
}
.box {
--primary-color: 5px;
border: solid var(--primary-color);
color: var(--primary-color);
}
效果如下:

可以看到,.box中的border引用自己作用域中注册的--primary-color,浏览器这个时候将其解析为border-width: 5px,而color也同时引用了--primary-color,可相当于color: 5px,此时浏览器将其继承祖先元素<body>的color值。
如果你尝试着将.box{}中的--primary-color禁用,此时border和color中的--primary-color将会引用全局的(即:root{})中注册的值(--primary-color: #0055fe)。此时border中的var(--primary-color)被浏览器解析为border-color: #0055fe,而border-width被解析为medium。而color中的var(--primary-color)也就是一个有效值了。

感觉是不是有点混乱呢?其实这里有一个关于CSS自定义属性(变量)的作用域概念。稍后再探讨。
CSS中的自定义属性的值类型没有任何约束,也就造成上面示例中提到的效果。如果你想对自定义属性值的类型有较强约束的话,就可以使用CSS Houdini的变量了,因为它有syntax属性来指定自定义属性的值类型。比如:
@property --primary-color {
syntax: '<color>';
initial-value: #0055fe;
inherits: false;
}
这样做除了指定了--primary-color值类型之外,还可以在var()直接引用--primary-color(会解析成初始值,即initial-value指定的值)。如果你在var()中引入的--primary-color自定义属性的值不是<color>类型,浏览器引用其初始值,比如:
@property --primary-color {
syntax: '<color>';
initial-value: #0055fe;
inherits: false;
}
/* 使用 --primary-color初始值 */
.initial__value {
border: 5px solid var(--primary-color);
}
/* 重置 --primary-color值 */
.new__value {
--primary-color: #09f;
border: 5px solid var(--primary-color);
}
/* 无效值,但会引用--primary-color初始值 */
.invalid__value {
--primary-color: 5px;
border: solid var(--primary-color);
color: var(--primary-color);
}
效果如下:
如需转载,烦请注明出处:https://www.w3cplus.com/css/css-custom-properties-you-know-how-much.html
如果文章中有不对之处,烦请各位大神拍正。如果你觉得这篇文章对你有所帮助,打个赏,让我有更大的动力去创作。(^_^)。看完了?还不过瘾?点击向作者提问!
