首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

你应该掌握的CSS自定义属性技术点

特别声明:此篇文章内容来源于@Ohans Emmanuel的《Everything you need to know about CSS Variables》一文。

大多数编程语言都支持变量。但遗憾的是,CSS从一开始就缺乏对原生变量的支持。如果写CSS的话,那是没有变量的,除非你使用像Sass这样的CSS处理器。

变量是Sass这样处理器的一个非常有用的特性之一。这也是你尝试使用的理由之一。Web技术发展是非常快速的。我很高兴地告诉你,CSS现在终于支持原生的变量了。

虽然CSS处理器还支持更多的特性,但是CSS添加原生的变量是很好的。这些举措使用Web更接近未来的技术。在这篇文章接下来的内容中,我将向你展示如何在CSS使用变量,以及如何使用它们让你的工作变得更轻松。

特别声明:为了能让CSS的原生变量与CSS处理器变量区分出来,我更喜欢将其称为CSS自定义属性。

你将学到

首先会向大家介绍CSS自定义属性的基本知识。我相信试图理解CSS自定义属性都必须从这一点开始。学习CSS自定义属性的基本知识是非常有意思的事情。其中更有趣的是,你可以在真实的项目中使用这些基本原理。

因此,我将通过三个简单的项目来向你展示CSS自定义属性的易用性。先来快速预览一下这三个项目:

项目1:使用CSS自定义属性创建组件变量

你可能在项目中已经使用到了组件变量。不管是React、Angular或者Vue中,使用CSS自定义属性都会使这个过程变得更简单。

项目2:使用CSS自定义属性实现皮肤切换

你可能在某个地方看到过类似的效果。在这个项目中将向你展示使用CSS自定义属性是如何简单的为Web网站实现皮肤切换的效果。

项目3: 创建CSS自定义属性展台(Booth)

这是最后一个项目。请不要介意这个名字。说实在的,我想不出一个更好的名字了。

注意如何动态更新容器的颜色,以及如何在input(type="range")控制条的范围内更改外部容器的3D旋转效果。

这个项目演示了使用JavaScript更新CSS自定义属性的便利性,以及即改即得的效果。你可以在上面示例中的input框中输入任意颜色值或者拖动range的进度条,浏览对应的效果变化。

为什么CSS自定义属性如此重要?

如果你是第一次接触CSS处理器中的变量或者CSS自定义属性,那么接下来介绍的几个方面将告诉你为什么CSS自定义属性对你而言是多么的重要。

理由1:代码更具可读性

不做过多的阐述,直接告诉你,使用CSS自定义属性可以让你的代码变得更具可读性和可维护性。

理由2:在大型项目中更易于修改

如果你将所有常量保存在一个单独的文件中,那么你想对一个变量进行更改时,不必跳过数千行代码。它变得很宽松,你可以把它放在任何一个地方。

理由3:更暴打发现输入错误

通过代码来查找错误是一件极为痛苦的事情。特别是你的错误是由于一个简单输入引起的,那就更令人恼火了。因为它是极难被人发现。CSS自定义属性的使用将消除这些麻烦。

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

我们需要感谢 CSS自定义属性,让我们可以在CSS中使用原生的变量,而不再需要借助于类似Sass这样的CSS处理器。

定义CSS自定义属性

我们从一些熟悉的东西开始:JavaScript中的变量

一个简单的JavaScript变量可以像下面这样来声明:

var amAwesome;

然后你可以给它赋值,比如:

amAwesome = "awesome string"

在CSS中,一个CSS自定义属性是以两个破折号(--)开始的任何名称。

CSS自定义属性作用域

还有一件事你需要注意。

请记住,在JavaScript中,变量是有作用域一说。它们可能是全局作用域,也有可能是局部作用域。那么在CSS中,CSS自定义属性也有这样的说法

比如下面这个示例:

:root {  

   --main-color: red}

:root选择器可以选择到DOM元素中或document树中最高顶级的元素。因此,在:root选择器是声明的CSS自定义属性,其作用域的范围是全局范围,也就是全局作用域。

明白了?

示例

假设你想创建一个CSS自定义属性,该自定义属性存储Web网站皮肤的主色(Primary color)。那么你将会怎么做呢?

你将创建选择器范围。使用:root创建一个global自定义属性:

:root {}

然后声明自定义属性:

:root {  

   --primary-color: red;

}

请记住,CSS自定义属性可以是任何名称,但名称前必须要以两个破折号,比如--color。

这是不是很简单。

使用CSS自定义属性

一旦声明了一个CSS自定义属性,并给其指定了一个值,那么你就可以在CSS的属性值中使用它。不过使用还是有点小问题。

如果你是在CSS处理器中使用,那么你必须在属性值中引用这个已声明的变量,例如:

$font-size: 20px;

.test {  

   font-size: $font-size;

}

但在CSS中使用已声明的CSS自定义属性,和在CSS处理器中使用声明的变量略有不同。你需要通过var()函数来引用已声明的CSS自定义属性。

比如上面的示例,在CSS中使用已声明的CSS自定义属性,需要像下面这样使用:

一旦你理解了这一点,你就会开始喜欢CSS自定义属性,而且会很喜欢。

不过需要特别注意的是,CSS自定义属性不像Sass(或其他CSS处理器)中的变量,可以在许多地方使用变量,并且可以进行一些数学运算,但使用CSS自定义属性时,只能在CSS属性值中使用CSS自定义属性

也不能像Sass这样的处理器一样直接做一些数学计算。如果你需要做一些数据计算,需要使用CSS的calc()函数。我们将在后续的示例中会聊到这一点。

使用CSS自定义属性要做一些数学计算时,应该像下面这样通过calc()函数来完成:

值得注意的地方

以下几个点是值得一提的地方。

自定义属性是普通属性,可以在任意元素上声明它们

CSS自定义属性可以在任意元素上,比如p、section、aside、根元素,甚至是伪元素上声明。他们都会按照预期进行工作。

当你在对应的元素中调用相应的CSS自定义属性时,可以看到他们都能按照你的预期工作:

CSS自定义属性可以通过继承和级联规则来解决

比如下面这段示例代码:

和正常变量一样,--color将会继承div中的值:

CSS自定义属性可以通过@media和其他条件规则来实现

和其他属性一样,可以在@media或其他条件规则中更改CSS自定义属性的值。

比如下面这段示例代码,在较大的屏幕设备上将会改变gutter的值:

CSS自定义属性可以用在HTML元素的style属性中

你可以选择在内联样式中声明CSS自定义属性,它同样是可以按照你的预期进行工作。

值得一提的是:CSS自定义属性是区分大小写的。这一点需要特别的注意。

解决多个声明的CSS自定义属性

CSS自定义属性和其他CSS属性一样,可以用标准级联解决多个相同声明的CSS自定义属性。比如下面这个示例:

就上面声明的CSS自定义属性,下列元素的文本颜色将会是什么颜色呢?

你能基于上述的知识点猜出来结果吗?

第一个元素是blue。因为在p选择器上没有显式定义--color,所以它将继承来自:root中的--color的值。

:root { --color: blue; }

第一个元素将是green。这一点非常的明显,因为在div选择器上显示的声明了--color自定义属性的值为green:

div { --color: green; }

ID名为#alert的div元素,它的颜色不再是green,将会是red,那是因为在#alert选择器中也显式的声明了--color的值为red:

#alert { --color: red; }

该ID选择器直接声明了一个自定义属性的作用域,其定义的值将会覆盖前面div声明的自定义属性,那是因为#alert选择器的权重高于div选择器。

由于最后一个元素在#alert内,所以它的颜色也将是red。

在p元素上没有显式声明CSS自定义属性,但在:root元素上显式的声明了,因为正如你所期望的一样,其颜色为blue。

:root { --color: blue; }

但CSS自定义属性和CSS中的其他属性是类似的,也具有继承这样的特性。所以最后一个元素会继承其父元素#alert中声明的CSS自定义属性值:

#alert { --color: red; }

解决循环依赖

循环依赖的发生方式如下。

当一个变量依赖于它自己。也就是说,它使用的是引用自身的var()。

另外一种方式就是两个或多个变量相互引用时:

目前唯一可破的方法是:不要在代码中创建具有循环依赖关系的CSS自定义属性

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

如果是语法错误,将会直接视为无效,但无效的var()将会被该属性的初始值或继承值替代。比如下面的示例:

如你所预期的一样,--color被var()替换了,但是属性值background-color:20px是一个无效值。由于background-color不是一个可继承的属性,所以该值将默认为background-color的初值值transparent。

请注意,如果你已经写了background-color: 20px没有自定认属性来替换,那么background-clor将是无效的。要是前面有对应的声明,将会使用前面的声明。

在构建单个指令时要小心

当你设置如下所示的属性值时,20px被解释为单个指令(Tokens)。

font-size: 20px

一个简单的方法是,20px的值被视为一个单独的实体。在使用CSS自定义属性构建单个指令时,需要特别的小心。比如下面这段示例代码:

你可能已经预料到font-size的值会产生20px,但这是错误的使用方式。浏览器会将其解释为20 px。注意20和px之间有一个空格

因此,如果你必须创建单个指令,则用一个CSS自定义属性来表示整个指令,比如--size:20px或者使用calc()函数,比如calc(var(--size) * 1px)(当--size设置为20时)。

如果你对这一点不怎么理解,没关系,在接下来的示例中会详细的解释这部分内容。

我们来动手做点东西

这是我们这篇文章中一直期待的一部分。将能过构建一些有用的项目来实践前面所讨论到的相关概念。还等什么呢?我们开始吧。

使用CSS自定义属性创建组件变量

假设我们要创建两个不同的按钮。这两个按钮的样式基本相同,只是略有一些细节不一样。

在这个示例中,只有background-color和border-color样式不同。如果你来做,你将会怎么做呢?这就是典型的解决方案。

创建一个基类,比如说.btn并添加变化的类。这个示例的结构如下:

.btn基类涵盖了按钮的基本样式。比如:

那么,对于略有差异的按钮效果怎么来实现呢?看下面的代码:

你知道我们是如何复制代码的吧。不过我们使用CSS自定义属性来做会做得更好。那么要做的第一步将是什么呢?

使用CSS自定义属性替换不同的颜色,但不要忘记为其添加默认值。

当你设置background:var(--color,black)时,其意思就是background的值是CSS自定义属性--color的值。当--color不存在时,就会使用其默认值black。

这就是给CSS自定义属性设置默认值的方法。就像JavaScript或其他编程语言一样。这样使用是很有意义的。

对于有差异的按钮,你只需要像下面这样提供CSS自定义属性的新值就可以:

.btn.red {  

   --color: red}

这就是两个按钮所有的样式代码。现在,当你使用.red类名时,浏览器会解析出不同的颜色值,并立即更新按钮的样式效果。

如果你花大量时间构建可重用的组件,像这样使用CSS自定义属性就会很方便。来看看他们之间对比的截图:

如果在你的组件库中,还有其他风格的按钮效果,使用CSS自定义属性,你就可以省去很多额外的时间:

使用CSS自定义属性切换Web站点的皮肤

切换Web网站的皮肤效果,我想你以前肯定有碰到过。比如像下面这样的效果:

那么这样的一个效果,使用CSS自定义属性能有多简单呢?我们来一起看看。

在此之前,我想先提一下,这个例子非常的重要。在这个示例中,将会涉及到使用JavaScript更新CSS自定义属性的一个方法。

我们真正想做的

CSS自定义属性美妙之处在于它们的响应性。一旦它们被更新,只要属性的值使用了CSS自定义属性,就将会被更新。

从概念上讲,下面这张图,能很好的解释整个过程。

因此,需要通过JavaScript给按钮添加点击事件的监听。

对于这个简单的示例,都是基于CSS自定义属性来对整个页面的背景颜色和文本颜色做切换。也就是说,当用户点击上面的任一按钮时,他们将CSS自定义属性设置为其他颜色。因此,页面的背景颜色和文本颜色会做相应的变化,从而实现切换皮肤的效果。

模板结构

页面所需的模板结果如下:

   ...

在.theme容器中包含了三个button。另外为了节约篇幅,把article元素中的内容省略了。

页面样式

这个项目的成功之处就是页面的样式。而其中的诀窍又很简单。我们没有直接设置background-color和color的值,而是基于CSS自定义属性来给他们设置属性值。下面就是我想要表达的意思:

body {  

   background-color: var(--bg, white);    color: var(--bg-text, black)

}

要实现切换皮肤的效果,原因很简单。每当单击一个按钮时,将要更改body中对应的两个CSS自定义属性的值。在更改之后,页面的整体样式将被更新。是不是非常的简单。

接下来,我们看看怎么通过JavaScript来实现CSS自定义属性值的更新。

添加JavaScript代码

下面是这个示例效果所需的所有JavaScript代码:

const root = document.documentElement const themeBtns = document.querySelectorAll('.theme > button')themeBtns.forEach((btn) => {    btn.addEventListener('click', handleThemeUpdate)})function handleThemeUpdate(e) {    switch(e.target.value) {        case 'dark':            root.style.setProperty('--bg', 'black')            root.style.setProperty('--bg-text', 'white')            break        case 'calm':            root.style.setProperty('--bg', '#B3E5FC')            root.style.setProperty('--bg-text', '#37474F')            break        case 'light':            root.style.setProperty('--bg', 'white')            root.style.setProperty('--bg-text', 'black')            break    }}

看到上面这一坨JavaScript代码,千万别吓到你。事实上这比你想像的要简单得多。

首先,通过const root = document.documentElement来获取根元素。这里的根元素指的就是元素。明白这一点是非常重要的。如果你感到好奇,这并奇怪,因为它需要设置CSS自定义属性的新值。

同样的,可以通过const themeBtns = document.querySelectorAll('.theme > button')选到.theme元素下的所有button元素。querySelectorAll会生成一个类似数组的数据结构,需要对这个数组进行循环遍历。遍历每个按钮,并给其添加一个单击事件监听器。具体方法如下:

themeBtns.forEach((btn) => {    btn.addEventListener('click', handleThemeUpdate)})

接下来我们解释一下handleThemeUpdate函数。每个按钮补点击时都会有一个handleThemeUpdate作为它的回调函数。注意,什么按钮被点击,然后执行正确的操作变得非常重要。

在此基础上,使用了一个operator开关器,并根据被单击的按钮的值执行一些操作。现在你再看一下JavaScript代码,你就能很轻易的理解它了。

构建CSS自定义属性展台

接下来我们将要构建一个CSS自定义属性展台:

记住,盒子的颜色是动态更新的,并且盒子的容器在三维空间中旋转,而且是随着range输入的值做想应的角度旋转:

你可以在文章前面的示例中体验一下案例的效果。这是使用JavaScript更新CSS自定义属性的典型案例之一。

接下来,咱们一起看看这个案例是怎么实现的。

模板结构

以下是所需的组件:

一个range的输入框

存放指令的容器

包含其他输入框的容器,以及每个输入框所包含的输入字段

结构很简单:

有几个方面需要注意:

input type=range的值的范围是-50到50,每次移动的值是5,其最小输入值为-50

如果你不确定这个range输入是如何工作的,可以先查阅相关的文档

请注意如何使用.color-boxes和.color-box容器类名,在这些容器中存在input

值得一提的是,第一个input的默认值是red

了解了文档的结构之后,就可以这样做了:

将.slider和.instructions容器设置为绝对定位,让其脱离文档流

给body元素设置一个background-color,并且在其左下角添加一个花的背景图

将.color-boxes容器放置在页面的正中心

给.color-boxes容器添加样式

先做这些处理吧。

代码并不像你想像的那么复杂。我希望你能理解它。如果你不理解上面的代码,那么你可以通过下面的评论与我一起交流和讨论。

对于body的效果需要更多的代码。由于我们使用background-color和background-image来给body设置样式。所以使用background的简写属性来设置多个背景可能是最好的一种选择。

body {  

   margin: 0;    color: rgba(255,255,255,0.9);    background: url('http://bit.ly/2FiPrRA') 0 100%/340px no-repeat, var(--primary-color);    font-family: 'Shadows Into Light Two', cursive;

}

url指向的是向日葵图片的地址,接下来的一组属性0 100%表示背景图像的位置。接下来的内容简单的介绍了CSS背景图像定位的基本原理。

/后的另一部分表示background-size的值。如果你把这个值缩小,那么背景图像也会相应的缩小。no-repeat你可能知道其意思。它用来防止背景图像不重复铺满body元素。最后,逗号后面的任何东西都是用来声明第二个背景的。在我们这个示例中,第二个背景只设置了一个背景颜色,而且其值是var(--primary-color)。

你一看就知道,它就是一个CSS自定义属性。这也意味着你必须先声明这个CSS自定义属性。比如:

:root {    --primary-color: rgba(241,196,15 ,1)}

--primary-color的原值是yellow。这没什么大不了的,接下来我们很快会在那里设置更多的CSS自定义属性。现在我们要做的是将.color-boxes放到页面的正中间:

main.booth {  

   min-height: 100vh;    display: flex;    justify-content: center;    align-items: center;

}

main容器是一个Flex容器,并正确地将其子元素设置到页面的正中心位置。另外再把.color-boxes和他的子元素做得更好看一点。

.color-box {  

   padding: 1rem 3.5rem;    margin-bottom: 0.5rem;    border: 1px solid rgba(255,255,255,0.2);    border-radius: 0.3rem;    box-shadow: 10px 10px 30px rgba(0,0,0,0.4);

}

设置了一个阴影效果。效果看上去有一些酷酷的感觉。咱们继续给.color-boxes容器添加一些样式:

.color-boxes {  

   background: var(--secondary-color);    box-shadow: 10px 10px 30px rgba(0,0,0,0.4);    border-radius: 0.3rem;    transform: perspective(500px) rotateY( calc(var(--slider) * 1deg));    transition: transform 0.3s}

又是一坨代码,感觉好复杂的样子。事实上将其拆分一下,就会显得简单得多。

.color-boxes {  

   background: var(--secondary-color);    box-shadow: 10px 10px 30px rgba(0,0,0,0.4);    border-radius: 0.3rem;

}

你知道这是什么吗?在这里有一个新的CSS自定义属性,我们应该将其添加到:root选择器中。

:root {  

   --primary-color: rgba(241,196,15 ,1);    --secondary-color: red;

}

第二种颜色是red。这将给容器设置一个red背景色。

接下来的代码可能会让部分同学感到困惑:

.color-boxes {  

   transform: perspective(500px) rotateY( calc(var(--slider) * 1deg));    transition: transform 0.3s}

暂时,我们可以简化上面的transform属性的值。

例如:

transform: perspective(500px) rotateY( 30deg);

transform使用了两个不同的函数。一个是perspective(),另一个是rotateY()。那么这两个函数有什么作用呢?

perspective()函数应用于3D空间中被变换的函数。它将激活三维空间,并在z轴上赋予元素深度。

有关于perspective()这方面更多的资料,你可以点击这里或这里进行了解。

rotateY()函数又是怎么一回事呢?通过perspective()函数激活的三维空间中,元素有x、y、z等平面,其中rotateY()函数会让元素沿着y平面旋转。

下图来自于codrops的图,能更好的帮助你理解这方面相关的知识。

有关于transform方面更多的介绍,可以点击这里进行了解。

我希望这些能帮助你消除一些压力。那我们回到开始的地方吧。

当你移动滑块时,你知道什么函数会影响.container-box的旋转吗?

它会被调用的rotateY()函数,让这个盒子沿着Y轴旋转。由于传入rotateY()函数的值将是通过JavaScript来更新,因此它的值应该用一个CSS自定义属性来表示。

那么为什么用1deg乘以这个CSS自定义属性呢?

从经验上来说,建议构建单一的指令时,将值存储在没有带单位的CSS自定义属性中。然后通过calc()函数将它们转换成任何你想要的单位。

当你有这些值的时候,你可以用这些值做任何你想做的事情。想要转换成deg或vw,你都可以任意选择。

在这种情况下,通过将number值乘以1deg来把数字转换成带有单位的值。

由于CSS是不理解数学计算的,所以你必须通过calc()函数来完成。一旦完成,我们就变得容易多了。这个变量的值可以在JavaScript中进行更新。

对于CSS来说,现在只剩下一点点了。

首先使用:nth-child选择器来选择每个子元素。

这里你需要有一些远见。我们知道我们将更新每个盒子的背景颜色。我们还知道,这个背景颜色必须由一个CSS自定义属性来表示,所以它可以通过JavaScript来访问。对吧。

因此,我们可以这样做:

.color-box:nth-child(1) {    background: var(--bg-1)}

非常容易的一件事。不过有一个问题。如果这个变量不存在,又会发生什么呢?根据前面介绍的内容,针对这一问题,我们需要做一个降级处理。就是添加一个默认颜色,比如像下面这样:

.color-box:nth-child(1) {    background: var(--bg-1, red)}

在这个特殊的案例中,我选择不提供任何的降级处理。

如果在属性值中使用CSS自定义属性无效,则该属性将接受其初始值。这一点前面已经提到过了。因此,当--bg-1无效或不可用时,background将默认为其初始值transparent.

初始值是指当属性没有显式设置时的值。例如,如果不设置元素的背景颜色,它将默认为transparent。初始值是一种默认的属性值。

加入一些JavaScript代码

对于JavaScript代码,我们要做的事情会很少。

首先让我们来处理滑块。仅只需要五行代码就可以了。

const root = document.documentElementconst range = document.querySelector('.booth-slider')range.addEventListener('input', handleSlider)function handleSlider (e) {    let value = e.target.value    root.style.setProperty('--slider', value)}

这对你而言是不是很简单,对吧。

首先,通过const range = document.querySelector('.booth-slider')选到了滑块元素。然后使用range.addEventListener('input', handleSlider)给input元素添加一个监听事件,当input的范围值得到改变时,将会调用handleSlider回调函数。

接下来做的就是编写handleSlider回调函数:

function handleSlider (e) {    let value = e.target.value    root.style.setProperty('--slider', value)}

root.style.setProperty('--slider', value)的意思是获取根元素的style样式,然后设置其属性值。

处理颜色变化

就像处理滑块值的变化一样简单。

const inputs = document.querySelectorAll('.color-box > input')inputs.forEach(input => {    input.addEventListener('input', handleInputChange)})function handleInputChange (e) {    let value = e.target.value    let inputId = e.target.parentNode.id    let inputBg = `--bg-${inputId}`    root.style.setProperty(inputBg, value)}

通过const inputs = document.querySelectorAll('.color-box > input')获取所有input元素。然后给所有input设置一个事件监听器:

inputs.forEach(input => {    input.addEventListener('input', handleInputChange)})

写handleInputChange回调函数:

function handleInputChange (e) {    let value = e.target.value    let inputId = e.target.parentNode.id    let inputBg = `--bg-${inputId}`    root.style.setProperty(inputBg, value)}

唷,唷,唷.......就是这么的简单。全部搞定!

还错过了什么

当我注意到我在任何地方都没有提到浏览器对CSS自定义属性的支持时,我已经完成了这篇文章的初稿。现在也该来收拾一下相应的残局了。

浏览器对CSS自定义属性的支持并不坏。可以说相当的不错,到目前为止所有的现代浏览器中都对CSS自定义属性做了良好的支持。在写这篇文章的时候,已经超过87%了。

那么问题来了,我现在可以在项目中使用CSS自定义属性了?我的回答是的!不过,一定要检查一下你自己的用户群体。

值得庆幸的是,你可以使用Myth这样的处理器。它将把一些CSS未来的特性做了相应的处理。言外之意,还未支持的CSS特性,你现在就可以使用,听起来是不是很爽。难道不是吗?

如果你有使用PostCSS的经验,那么这同样是使用未来CSS的一个很好的方法。这里有一个CSS自定义属性的PostCSS模块。

如果你从未接触过PostCSS相关的东西,那么你有必要花点时间去了解或者学习一下了。

那今天就到这里吧。这就是我要和大家聊的东西,有关于CSS自定义属性,应该掌握的一些知识点。

文章涉及到图片和代码,如果展示不全给您带来不好的阅读体验,欢迎点击文章底部的 阅读全文。如果您觉得小站的内容对您的工作或学习有所帮助,欢迎关注此公众号。

W3cplus.com

————————————

记述前端那些事,引领web前沿

长按二维码,关注W3cplus

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180411B1VU9N00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券