Apache Echarts 5 线上发布会

CSS自定义属性你知多少

特别声明:如果您喜欢小站的内容,可以点击申请会员进行全站阅读。如果您对付费阅读有任何建议或想法,欢迎发送邮件至: 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-colorvar()引用,而--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))计算出来的transfromnone

那这就引出了第一个问题:当一个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);
}

你将看到的效果如下:

由于手误,在bordercolorvar()函数事实上引用了一个并未定义的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,因此.boxcolor继承了bodycolor值,即#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禁用,此时bordercolor中的--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);
}

效果如下:

剩余80%内容付费后可查看

如需转载,烦请注明出处:https://www.w3cplus.com/css/css-custom-properties-you-know-how-much.html

如果文章中有不对之处,烦请各位大神拍正。如果你觉得这篇文章对你有所帮助,打个赏,让我有更大的动力去创作。(^_^)。看完了?还不过瘾?点击向作者提问!

赏杯咖啡,鼓励他创作更多优质内容!
返回顶部