认识CSS自定义变量
CSS自定义属性?听着怎么那么神奇呢,属性还可以自定义,那不是可以放肆地玩耍?我自己定义的属性浏览器都能认识?
一、基础
(一)名字和用途
其实CSS自定义属性还有很多小名,比如CSS变量、原生变量、CSS自定义属性级联变量,这些指的都是同个东西。
说到变量,在SCSS\LESS等CSS预处理器中大家都已经经常运用,说来有几点好处:
1、能使用颜色变量统一风格;
2、可以采用一致的组件属性,包括布局和定位等;
3、避免代码冗余。
那既然SCSS就能做到的东西,我们还有这个所谓的CSS自定义变量干什么呢?人家自然有它的独到之处。
1、比如可以在运行时改写,具备动态性;
2、比如方便使用JS读取和改写;
3、比如可继承、可组合、同时具有作用域。
在这几个方面,预处理器完全被CSS自定义变量KO了。
(二)声明变量
语法很简单,分成两步,声明变量和使用变量。
声明变量使用的是--
前缀。
:root{
--*: xxxx;
/* --variety-name: variety-value; */
}
这里需要注意几个点:
:root
匹配的就是HTML中的<html>
元素,具有最高的权重,:root
声明的变量就是全局变量;- CSS自定义变量对大小写敏感,
--color
和--Color
是两个变量; - 变量必须声明在{}中,如果在这里把它理解为属性就更好记忆了,毕竟我们不会把CSS属性写到括号外边去;
- *号代表的就是我们给变量起的名字。起名字这事真的很烦人,还好CSS变量的名称限制很少,除了一些特殊关键字符不能使用,正常来说你用数字/字母/下划线_/短横线-都是没问题的,据说还可以使用中文、日文和韩文。
:root{
--黑色:#000;
}
body{
background: var(--黑色);
}
手贱如我试了一下,真的可以识别。但是,为了世界和平请答应我不要这么写。
(三)使用变量
很简单,就是我们很熟悉的var
关键字。
/* 定义变量 */
:root{
--*: #000;
/* 例如 --color-bg: #000; */
}
/* 使用变量 */
body{
background: var(--*);
/* 例如 background: var(--color-bg); */
}
还有一种设置默认值的使用,就是在变量名称后面,加上一个默认值。
.div{
background: var(--变量名称,[默认值]);
/* 例如 background: var(--color-bg, #000); */
}
也就是说,当这个变量没有被声明过的话,就会使用默认值,不至于没着没落的。
注意这里的情况是变量没有被声明过,要是变量是声明过的,但是使用起来是不合法的,那么就会采用原来属性的缺省默认值,并不是后面这个你设定的默认值。
p{
background-color: var(--color, #000);
/* --color没有声明过,所以这里的p元素背景颜色时候用了默认值#000 */
}
div{
--color: 20px;
background-color: var(--color, #000);
/* 很明显,background-color: 20px;是有语法错误的,所以这里div的背景色为透明,取的是这个属性的默认值 */
}
(四)作用域和权重
1、如果你需要定义一个全局的变量,那么可以放在
:root
根元素下面;
2、如果只需要在部分元素/组件下使用,就定义在相关的类下面;
3、另外还可以在@media
媒体查询中或者:hover
等伪类中使用。
理解了作用域,那么权重也是同样的道理,因为CSS自定义变量是可以继承的,所以权重跟我们平时的属性权重理解是一样的。
这里引用一下张鑫旭大神的例子给大家出道题。
:root{
--color: purple;
}
div{
--color: green;
}
#alert{
--color: red;
}
*{
color: var(--color);
}
<p>请问我是什么颜色</p>
<div>请问我是什么颜色</div>
<div id="alert">
请问我是什么颜色
<p>请问我是什么颜色</p>
<p style="--color: grey;">请问我是什么颜色</p>
</div>
答案如下:

(五)变量的其他组合
除了上述的一些用法,CSS自定义变量也可以使用calc()
函数进行计算,或者进行字符串拼接。
这里举了三个例子,具体的说明在注释中。
p{
--fz: 50;
font-size: var(--fz)px;
/* 不要太天真,这样是错的 */
}
p{
--fz: 50;
font-szie: calc(var(--fz) * 1px);
/* 如果你一定要这么用,可以使用calc计算函数 */
}
p::after{
--text: "hellp";
content: var(--text) " word";
/* 但是字符串的拼接是可以实现的 */
}
当然,变量不止可以直接使用,直接或者通过计算把值传递给另一个变量也是可行的。
p{
--fz: 20px;
--fz-lg: var(--fz);
font-size: var(--fz-lg);
/* 直接传递 */
}
p{
--fz: 20px;
--fz-lg: calc(var(--fz) * 1.5);
font-size: var(--fz-lg);
/* 通过计算后传递 */
}
二、在JS中使用
读:getPropertyValue( )
写:setProperty( )
比如说,你在:root
上定义了一个color变量,用于设置页面的主题色,那么通过下面的JS,你就可以很简单地改变color变量的值,从而改变页面的主题色。换个皮肤,so easy。
// 读取数据
const rootStyles = getComputedStyle(document.documentElement);
const varValue = rootStyles.getPropertyValue('--color').trim();
// 改写数据
document.documentElement.style.setProperty('--color', value);
当然用处不止用来换肤,充分发挥你的想象力,看看CSS变量与JS的结合能产生什么样奇妙的效果~附上大漠老师的小DEMO。

应用CSS自定义变量
介绍了这么多,那么CSS自定义变量到底都在什么场景下应用呢?
一、CSS禅意花园
最强的应用当然是一键换肤啦!
一般情况下,我们若是想根据不同的合作方或者不同的应用更换主题颜色,一般使用预处理器先定义一个全局主题颜色,如:
$theme-color: #f00;
button{
background: $theme-color;
}
最后编译得到一个定制的CSS文件,如:
button{
background: #f00;
}
这样我们就可以通过引入这个特殊的CSS文件,得到一套主题色为红色的页面样式。
第一种形式适用于,功能通用但是最后根据配置输出一个产品的独立管理台、一个独立小程序等,这样用自己一套独有的CSS文件就很方便。
但是如果我们提供一个通用的产品去接入不同的合作方,接入方都有定制主题的需求,就可以选择预处理器或CSS自定义属性。
/* 预处理器方式,先定义不同的主题色 */
$theme-color-a: #f00;
$theme-color-b: #0f0;
/* 在页面层级最外层加上定制的类名,类名中所有样式都需要重新覆盖一遍 */
.project-a{
button{
background: $theme-color-a;
}
a{
color: $theme-color-a;
}
}
.project-b{
button{
background: $theme-color-b;
}
a{
color: $theme-color-b;
}
}
/* 或者在媒体查询中需要重置样式 */
$fz-sm: 12px;
$fz-md: 14px;
button{
font-size: $fz-sm;
}
a{
font-size: $fz-sm;
}
@media (min-width: 375px){
button{
font-size: $fz-md;
}
a{
font-size: $fz-md;
}
}
:root{
--FZ: 12px;
}
/* 照常写一套样式 */
button{
background: var(--THEME-COLOR, #fff);
}
a{
color: var(--THEME-COLOR, #fff);
}
/* 根据不同的接入方设置主题色 */
.project-a{
--THEME-COLOR: #f00;
}
.project-b{
--THEME-COLOR: #0f0;
}
@media (min-width: 375px){
:root{
--FZ: 14px;
}
}
这种需求在CSS预处理器中无法实现一个没有复制代码的方案,总是需要覆盖实现的值和规则,这也经常会导致CSS冗余。
使用CSS自定义属性,解决方案是尽可能的简洁,也避免复制和粘贴代码,因为只要重新定义变量的值,不需要去覆盖一次样式。
第二种方式中两个方式的区别则是,预处理器中变量的作用域是无法继承的,而CSS自定义变量则相对灵活,这样一旦接入方多了之后,两种方式的代码量就会有质的区别。
以上两种方式都还是直接把主题色配置在CSS中。但是如果需要接入管理台,让合作方直接在管理台设置主题色的话,预处理器这种静态的方式就直接退出了竞争,CSS自定义属性的绝对优势就出来了。
:root{
--THEME-COLOR: #fff;
}
button{
background: var(--THEME-COLOR);
}
a{
color: var(--THEME-COLOR);
}
let value = #f00; //或者通过请求获取配置的主题色
document.documentElement.style.setProperty('--THEME-COLOR', value);
第三种方式则非常灵活,我们可以通过管理台或者其他配置的方式,传递合作方主题色,一键应用即可。
历史性的时刻诞生了,从此接入就是分分钟的事,直接读取管理台的配置,更改CSS自定义属性。来个小DEMO体验一下吧。

二、主题色处理
预处理器提供了很多高级的颜色方法,可以实现颜色的高亮、变暗或去饱和等等;
$color: #f00;
.lighten{
background: lighten($color,10%);
}
.darken{
background: darken($color,10%);
}
.desaturate{
background: desaturate($color,10%);
}
这样编译出来的结果就是
.lighten{
background: #ff3333;
}
.darken{
background: #cc0000;
}
.desaturate{
background: #f20d0d;
}
这些方法无法直接使用在CSS自定义属性中,但是我们可以通过rgb( )或者hsl( )来调整主题颜色的色调或亮度。
(一)使用rgb改变颜色
rgb颜色变亮变暗的原理相对简单,只需修改--COLOR-R
/--COLOR-G
/--COLOR-B
的值,利用calc函数对rgb的值进行线性增减即可。
:root{
--COLOR-R: 25;
--COLOR-G: 153;
--COLOR-B: 112;
--DARKEN: 30; // 加深程度
--LIGHTEN: 30; // 变亮程度
--THEME-COLOR: rgb(var(--COLOR-R), var(--COLOR-G), var(--COLOR-B));
--THEME-COLOR-DARKEN: rgb(calc(var(--COLOR-R) - var(--DARKEN)), calc(var(--COLOR-G) - var(--DARKEN)), calc(var(--COLOR-B) - var(--DARKEN)));
--THEME-COLOR-LIGHTEN: rgb(calc(var(--COLOR-R) + var(--LIGHTEN)), calc(var(--COLOR-G) + var(--LIGHTEN)), calc(var(--COLOR-B) + var(--LIGHTEN)));
}

(二)使用hsl改变颜色
rgb我们可能相对熟悉,但是hsl用得比较少,下面简单介绍一下hsl的原理,详细内容请点击链接。
与RGB使用的三色光不同,HSL同样使用了3个分量来描述色彩,HSL色彩的表述方式是:H(hue)色相,S(saturation)饱和度,以及L(lightness)亮度。
HSL的H(hue)分量,代表的是人眼所能感知的颜色范围,这些颜色分布在一个平面的色相环上,取值范围是0°到360°的圆心角,每个角度可以代表一种颜色。
HSL的S(saturation)分量,指的是色彩的饱和度,它用0%至100%的值描述了相同色相、明度下色彩纯度的变化。数值越大,颜色中的灰色越少,颜色越鲜艳,呈现一种从理性(灰度)到感性(纯色)的变化。
HSL的L(lightness)分量,指的是色彩的明度,作用是控制色彩的明暗变化。它同样使用了0%至100%的取值范围。数值越小,色彩越暗,越接近于黑色;数值越大,色彩越亮,越接近于白色。
一般来说,我们需要按钮在hover状态时加深颜色,此时应用的原理是将颜色的hsl值中的L也就是亮度调低。
:root{
--COLOR-H: 29;
--COLOR-S: 100;
--COLOR-L: 50;
--DARKEN: 0.15;
--THEME-COLOR: hsl(var(--COLOR-H), calc(var(--COLOR-S) * 1%), calc(var(--COLOR-L) * 1%));
--THEME-COLOR-DARKEN: hsl(var(--COLOR-H), calc(var(--COLOR-S) * 1%), calc(var(--COLOR-L) * (1 - var(--DARKEN)) * 1%));
--THEME-COLOR-LIGHTEN: hsl(var(--COLOR-H), calc(var(--COLOR-S) * 1%), calc(var(--COLOR-L) * (1 + var(--DARKEN)) * 1%));
}

(三)使用遮罩改变颜色
如果不通过改变色值来改变颜色的话,可以选择遮上一个半透明的蒙层来改变颜色,加深颜色则选择黑色半透明蒙层,提亮颜色则选择白色半透明蒙层。
.button_color{
position: relative;
color: #fff;
background: var(--THEME-COLOR);
border: 1px solid var(--THEME-COLOR);
&:after{
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transition: all 0.2s;
}
/* 黑色半透明蒙层 */
&:hover:after{
background: rgba(0,0,0,0.05);
}
/* 白色半透明蒙层 */
&.lighten:hover:after{
background: rgba(255,255,255,0.1);
}
}

三、兼容性
目前CSS自定义变量的兼容性还是比较可观的,新的主流浏览器都支持。
PC端主要是IE这块硬石头,而移动端则主要是低端机型系统例如ios9.2及以下/安卓4.4及以下不支持,这样的兼容性已经足以允许让我们在项目中开始使用CSS自定义属性,并对一些低端版本进行降低兼容处理。

对于不支持的浏览器可以采用下列兼容方式:
方案1:直接设置一个默认颜色
直接使用普通的属性定义,兼容所有的浏览器,保证显示正常。
button{
background: #F00;
/* 默认颜色,若不支持CSS自定义属性则应用该默认颜色 */
background: var(--THEME-COLOR, #F00);
/* 在实践中发现有些机型虽然能识别到CSS自定义变量但是无法获取正确颜色,只能获取到默认值 */
}
方案2:CSS根据@supports判断是否兼容并适配
低端机型也无法正确识别@supports,@supports的兼容性只比CSS自定义变量好一丢丢。
@supports ( (--a: 0)) {
/* supported */
}
@supports ( not (--a: 0)) {
/* not supported */
}
方案3:js根据@supports判断是否兼容并适配
const isSupported =
window.CSS &&
window.CSS.supports &&
window.CSS.supports('--a', 0);
if (isSupported) {
/* supported 引入支持自定义变量的CSS,允许更改CSS自定义变量 */
} else {
/* not supported 引入兼容的CSS文件 */
}
四、注意事项
- 在一些浏览器中,针对CSS变量的复杂
calc()
运算可能不能工作; - 进行
calc()
运算时,最好能提供默认值:calc(var(--base-line-height, 0) * 1rem)
; - 不能作为媒体查询值使用:
@media screen and (min-width: var(--desktop-breakpoint) ) {
};
- 图片地址,如
url( var(--image-url) )
,不会生效; - 因为CSS自定义变量对大小写敏感,故建议全局变量使用全大写形式,除了设置整体主题色,尽量减少改动全局变量;
- web端可将主题颜色等变量设置在根元素
html
,并通过上述方法修改自定义属性的值; - 小程序因无法获取DOM,无法直接修改CSS自定义属性的值,可以采取在页面的最外层元素设置行内样式的方式重置自定义属性的值,如:
.container{
--THEME-COLOR: #f00;
}
<view class="container" style="--THEME-COLOR: #0f0;">
<!-- 该结构下的元素,重置为行内样式的主题色 -->
</view>
五、小结
- CSS自定义变量目前已支持各主流浏览器,低端版本可以采用兼容方案;
- SCSS变量和CSS自定义变量有本质上的区别,用来解决不同场景下的问题,CSS自定义属性用于动态主题,预处理器变量用于静态模板,项目中可以根据情况结合运用效果更佳;
- 在媒体查询中使用自定义变量的话,这样响应式设计相关的逻辑与正常的设计虽然分离,但是无论我们在哪里看到var( )声明语句,我们都能很明显的知道这个属性会发生变化。而使用传统的CSS方式,我们是无法察觉这一点的,这样代码的可读性就高了很多。