图解CSS:CSS自定义属性

特别声明:为感谢社区广大朋友对小站的支持,自2019年10月1日至2019年11月11日开通年费VIP通道,年费价格为 ¥299.00元。如果您喜欢小站的内容,可以点击开通会员进行全站阅读。如果您对付费阅读有任何建议或想法,欢迎发送邮件至: airenliao@gmail.com!(^_^)

众所周之,CSS的维护一直是件不易的事情,特别是在构建大型Web站点或Web应用程序时,如果是多人协作的话难度更大。另外,由于CSS语言是一种声明式语言,而且不像其他语言有变量、条件和逻辑等特性,一直生存在程序语言鄙视链的最底层。也因为这个原因,社区中有了各种CSS处理器语言,比如Sass、LESS和Stylus等。这些处理器语言引入了一些类似其他程序语言的特性,比如变量、运算符和逻辑运算等。

虽然CSS处理器给编写和维护CSS带来了一些便利,但还是需要额外的编译。不过处理器中的变量着实为我们带来很大的优势,也正因为如此,社区开始将CSS处理器中的变量引入到原生CSS中,经过多年的推进和演变才有了今天的CSS自定义属性。

接下来在这一章中,将和大家一起探讨CSS自定义属性。

CSS 自定义属性简介

CSS自定义属性已进入到W3C规范的 TR阶段,纳入在一个独立的模块中,即 CSS Custom Properties for Cascading Variables Module Level 1。 该模块引入了一系列作者(CSSer)自己定义的属性,这些属性统称为自定义属性,允许作者自由的选择名称,自由的为名称属性分配任意值。这些属性能够提供给var()函数使用,被var()函数引用的自定义属性又常被称为变量

这样一来,CSSer声明的这些自由属性就有了两个名称:自定义属性变量

  • 自定义属性:使用--**代表任意声明的名称)声明的特殊格式作为名称,该名称被称为自定义属性,同时可以给自定义属性赋予任何值。比如--color: #fff
  • 变量:CSS的var()函数引用的自定义属性被称为变量var()会返回自定义属性所对应的值,同时可以被运用于相应的CSS属性。对应的即是CSS规则中的属性值

用张图来描述他们之间的关系:

CSS自定义属性的作用

如果你使用过任何编程语言,变量这个词(概念)并不会陌生。在一些命令式编程语言中(比如我们前端熟悉的JavaScript),可以通过变量让我们更好的跟踪某些状态。变量是一种符号,关联着一个特定的值,变量的值能随着时间的推移而改变。变量的好处还在于我们可以把值存储在一个地方,然后在需要的地方调用它或者修改它。这样就不用在程序的不同地方为不同的值添加不同的变量:

所有变量更新使用同一个存储地址

而在CSS这种声明式语言,她是缺乏动态性的。也无法做到随着时间而改变的值并不存在,也就没有所谓变量的概念。可事实上,我们又非常的期待CSS也能和其他的编程语言一样,能随着周围环境和开发者的需求做出不断变化。

在CSS自定义属性还没有出现的之前,CSS自身引入了一种层级变量的概念(稍后会提到),从而能够从容应对可维护性的挑战。这就会使得在整个CSS Tree中都可以象征性的引用一个变量。但这种象征性的变量往往不能很好的解决CSS的维护和扩展的问题,同时这种象征性的变量也足以让一些不了解CSS的同学头痛。这是后话,暂且不表。继续回到我们的实际场景中来。

我想不少同学都应该有过构建大型Web网站或Web应用的经历,使用的CSS数量是非常庞大的,并且在很多场合有着大量的重复使用。就拿网站的配色方案来举例,一些颜色在CSS文件中会出现很多次,并被重复使用。当你修改配色方案时,不论是调整某个颜色或完全修改整个配色,都会是一个复杂的问题。如果单纯的依赖全局的查找替换是远远不够,这样的操作难免会出错。

如果使用了CSS的框架,这种情况会变得尤其糟糕,此时如果要修改颜色,则需要对框架本身进行修改。虽然这些框架都有可能引入了Sass这样的CSS处理器帮助我们减少了出错的机会,提高了可维护性的能力,但这种通过添加额外步骤的方式(需要做编译处理),可能会增加系统的复杂性。

CSS自定义属性(CSS变量)的出现,为我们带来了一些CSS处理器的便利,并且不需要额外的编译。在CSS中使用CSS自定义属性的好处和在编写语言中使用变量的好处没有特别的不同之处。W3C规范上有过这样的一段描述:

使用CSS自定义属性使得大文件更易于阅读,因为看起来很随意的值有了一个提示信息的名字,并且编辑这些文件更加简单,更不易于出错。因为你只需要在自定义属性处修改一次,这个修改就会应用到使用该自定义属性的任何地方

简单地说,CSS自定义属性除了提供了更灵活的设置、引用和修改的便利性之外,还具有较强的语义化(这需要你对语义化有足够强的意识,比如primary这样的名称总是要比red这样的名称来得有意义)。这些语义化信息让你的CSS文件变得易读和理解。

为此,可读性和可维护性是CSS自定义属性最大的优势

CSS自定义属性语法和基础应用

在介绍CSS自定义属性时,我们可以从一些熟悉的东西开始着手,这样对于从后端转过来的同学更易于理解。我们就拿JavaScript中的变量来举例吧。

在JavaScript中,声明一个变量有多种方式,比如:

var customProperty

// 或
let customProperty = true

// 还可以
const customProperty = IS_ACTIVE

如果你对JavaScript和我一样的不熟悉,或者说你对CSS处理器有所接触过,我们可以拿CSS的处理器来举例。我们熟悉的几个CSS处理器,比如LESS、Sass和Stylus在声明变量的时候都有着自己的方式。通常会使用一个象征性的实体符来做为变量的前缀,比如说Sass中会使用$符,在LESS中使用@符,Stylus声明变量不带特殊前缀,直接使用表达式,比如primary-color=red

CSS自定义属性在声明的时候也使用了类似的方法,它引入了--符号做为前缀来声明一个自定义属性:

:root {
    --primary: #f36;
}

示例中的--primary就是一个我们所要说的CSS自定义属性。CSS自定义属性和常规CSS属性的用法是一样的。把它们当作动态属性会比变量更好。这意味着它们只能在声明块中使用。也就是说,自定义属性和选择器是强绑定的。可以是任何有效的选择器。

如果已声明的CSS自定义属性未被任何属性调用的话,将不会产生任何的效果。只会是一段字符串停留在你的样式文件中。

调用已声明的CSS自定义属性和其他CSS处理器中变量的调用略有不同。调用CSS自定义属性需要通过var()函数来引用。将CSS自定义属性当作var()函数的第一个参数传进去,并将整个函数赋值给CSS的属性(可以是CSS的属性,也可以是CSS的自定义属性),比如:

body {
    color: var(--primary);
}

.button {
    --primaryButton: var(--primary);
}

示例中的var()函数可以代替元素中任何属性中的值的任何部分。不过var()函数不能作为属性名选择器或者其他除了属性值之外的值

var()函数同时可以接受两个值:

var(<custom-property-name>, <declaration-value>)

其中<custom-property-name>是CSS的自定义属性;<declaration-value>是一个回退值,该值被用来在自定义属性值无效的情况下保证var()函数有值,能让CSS属性规则生效。比如:

:root {
    --primary: #f36;
}

.button {
    background-color: var(--primary, #fff);
    color: var(--color, #333);
}

CSS 自定义属性的使用

现在对CSS自定义属性有了一个基本的认识,接下来用几个小示例代码向大家演示CSS自定义属性的一些特性,从而增强对其认知。

CSS自定义属性和CSS属性工作原理完全相同

CSS自定义属性可以在任何元素、选择器,甚至是伪元素上声明的普通属性。他的使用和CSS属性的使用是相同的,原理也是相同的:

:root {
    --font-size: 1em;
}
p {
    font-size: var(--font-size);
}
section::after {
    font-size:1.5em;
}

CSS自定义属性和CSS属性一样具有继承和级联特性

CSS中有三个概念是学习CSS必须要掌握的,即层叠继承权重。CSS自定义属性同样的具备继承和级联等特性。比如说:

<!-- HTML -->
<div class="parent">
    <div class="child1"></div>
    <div class="child2"></div>
</div>

// CSS
.parent {
    --primary: #f36;
}

.child1 {
    background-color: var(--primary);
}

.child2 {
    color: var(--primary);
}

上面的示例中,.child1.child2都继承了他们父元素.parent中的--primary自定义属性。但在很多情况之下,我们不需要这样行为,我们可以在:root{}中来显式声明自定义属性:

:root {
    --primary: #f36;
}

.child1 {
    background-color: var(--primary);
}

.child2 {
    color: var(--primary)
}

这样可能不易于理解,拿一个更真实的案例来举例。比如说,每个Web应用都会有自己的下色系,就拿Bootstrap这个CSS框架的色系来说吧,它的主色系是--primary: #007bff,该色会用于多个地方,比如:

使用CSS继承的特性,可以把事情变得更简易:

:root{
    --primary: #007bff;
}

// Button组件
.btn-primary {
    background-color: var(--primary);
    border-color: var(--primary);
}

// Badge组件
.badge-primary {
    background-color: var(--primary);
    border-color: var(--primary);
}

// Dropdowns组件
.dropdown-primary {
    background-color: var(--primary);
    border-color: var(--primary);
}

// Pagination组件
.page-link {
    color: var(--primary);
}

// Progress组件
.progress-bar {
    background-color: var(--primary);
}

有朝一日,你的老板说这种颜色不想再看了,想换换色系,那么只需要调整:root中的--primary的值即可。

另外就是相同的模块组件只有略微性的差异,比如下图这样的一个效果:

对于这样的一个效果,借助CSS的级联特性,也可以将事情变得更容易:

:root {
    --color: #333;
}

.card {
    color: var(--card);

    &:nth-child(2) {
        --color: #2196F3;
    }

    &:nth-child(3) {
        --color: #f321ab;
    }
}

因为CSS的级联和继承是较为复杂的,为了更好的能让大家清楚CSS自定义属性使用时借助CSS的级联和继承彰显出自己特性(代码量更少,更易维护,更易扩展)。再为大家展示一个层级更深的示例:

<!-- HTML -->
<p>我是什么颜色?</p> 
<div>我又是什么颜色?</div>
<div id="alert">
    我是什么颜色?
    <p>我又是什么颜色?</p>
</div>

// CSS
:root {
    --color: #333;
}
div {
    --color: #2196F3;
}
#alert {
    --color: #f321ab;
}

结果用下图来阐述:

如果你想对CSS层叠和继承相关的知识做进一步了解的话,可以阅读《图解CSS:CSS层叠和继承》一文。也可以阅读@Miriam的《CSS Custom Properties In The Cascade》一文。

CSS自定义属性可以在行内style属性中使用

CSS自定义属性和CSS属性一样,可以在元素的style属性中使用CSS自定义属性。

<!-- HTML -->
<button style="--color: blue">Click Me</button>

// CSS
button {
    border: 1px solid var(--color);
}

button:hover {
    background-color: var(--color);
}

内联样式中使用CSS自定义属性非常有意义,特别是在通过JavaScript来操作CSS自定义属性的时候特别更有意义。

CSS自定义属性区分大小写

CSS自定义属性和CSS属性略有不同,CSS的属性不会区分大小写,但CSS自定义属性则会区分大小写。

:root {
    --COLOR: #fff;
    --color: #f36;
}

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

CSS自定义属性命名

CSS自定义属性的命名规则比较松散,可以是任何有效的字符,比如中文大写字母驼峰命名中距线emojiHTML实体等等:

CSS自定义属性支持回退参数

在介绍CSS自定义属性的时候,如果将CSS自定义属性当作var()函数的参数传进去的时候,它还支持第二个参数,即回退参数。而CSS的属性是不支持这一特性。

  • 如果CSS自定义属性不被浏览器支持,那么可以提供一个降级的参数以备能让浏览器识别
  • 如果浏览器支持CSS自定义属性,但并没有显式声明该CSS自定义属性的值,则会选择降级的参数
  • 如果浏览器支持CSS自定义属性,而且显式声明了该CSS自定义属性的值,则会选择CSS自定义属性的值,不会选择降级的参数

比如下面这个示例:

:root {
    --color: #f36;
}

.box {
    width: var(--w, 100px);
    color: var(--color, #fff);
    border-width: var(--color, 2px);
}

无效的CSS自定义属性将会发生什么?

无效的CSS自定义属性运用于一个CSS的属性时将会发生什么呢?在告诉大家会发生什么之前,我们先来看看在CSS的属性中使用了一个无效的值会发生什么?

我想我们都有过手误的经过,比如说,在做CR(Code Review)的时候会发现这样的现象,比如:

.card {
    padding: -10px;
}

padding是不支持负值,也就是说,-10px对于padding属性来说是一个无效的值,这个时候,浏览器在渲染的时候会采用padding的初始值(initial),即0

在使用CSS自定义属性的时候,如果CSS自定义属性在被调用时对于CSS属性来说是一个无效值时,也会采用initial做为降级处理,比如:

:root {
    --color: 20px;
}

.p {
    color: var(--color);
}

就上面的示例而言,声明的--color: 20px是一个有效的值,但其被用于color属性时,--color是一个无效的值,因为20px对于color属性而言是一个无效的值。在这种情形之下,color属性会采用其初始值initial(取决于用户代理)。如果该元素的父辈元素没有显式设置color的值,那么会继承<html>元素的color值,在Chrome浏览器下会是一个#000的颜色值。

还有另外一个情景,虽然在调用已声明的CSS自定义属性是一个无效的值,但提供了一个降级值,而且该降级的值是一个有效的值,那么就不会采用initial值,而是会采用降级值,比如:

:root {
    --color: 20px;
}

p {
    color: var(--color, blue);
}

链式的CSS自定义属性

使用var()函数调用已声明的CSS自定义属性时,给var()提供降级参数时,我们可以使用链式的方式提供降级参数,比如:

p {
    --color1: red;
    --color2: blueviolet;
    --color3: orange;

    color: var(--color1, var(--color2, var(--color3, blue)));
}

循环依赖的CSS自定义属性是无效的

CSS是一门声明性的语言,元素的样式规则没有顺序的概念(相同的属性出现在同一个选择器块内,后者会覆盖前者)。它只能有一个值,它不可能同时是以前值和它的值加1,因此这形成了一个循环。

我们先从JavaScript中来讲起,比如:

var a = 1;
var a = a;
console.log(a); // » 1

我们再来看CSS自定义属性的循环使用:

:root {
    --size: 10px;
    --size: var(--size);
}

body {
    font-size: var(--size, 2rem);
}

在CSS中,相同的CSS属性在同一个选择器块内,后者会覆盖前者,比如上面的示例中--size: var(--size)将会覆盖--size。但CSS自定义属性如果其值依赖于自身的话,即,它使用的是引用自身的var(),该值是无效的。上例中--size自定义属性无效,在body中调用--size时无效,此时采用了var()的降级值2rem

除了自定义属性引用自身外,还有另外一情景,那就是两个或多个自定义属性之间相互引用:

:root {
    --one: calc(var(--two) + 10px);
    --two: calc(var(--one) - 10px);
}

这种相互引用的CSS自定义属性也是无效值。目前唯一可破的方式是:不要在代码中创建具有循环依赖关系的CSS自定义属性

带基本运算符的CSS自定义属性

熟悉CSS处理器的同学应该都知道,CSS处理器中可以使用运算符相关的操作。该特性其实在CSS中借助calc()函数也可以做一些基本运算符相关的操作。在CSS自定义属性中具有同样的特性:

:root {
    --indent-size: 10px;

    --indent-xl: calc(var(--indent-size) * 2);
    --indent-l: cal
剩余80%内容付费后可查看

如需转载,烦请注明出处:https://www.w3cplus.com/css/css-custom-property.html

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

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