# css变量

返回:前端基础知识归纳

TIP

其实CSS自定义属性还有很多小名,比如CSS变量、原生变量、CSS自定义属性级联变量,这些指的都是同个东西。

SCSS\LESS

说到变量,在SCSS\LESS等CSS预处理器中大家都已经经常运用,说来有几点好处:

  • 1、能使用颜色变量统一风格;
  • 2、可以采用一致的组件属性,包括布局和定位等;
  • 3、避免代码冗余。

CSS自定义变量

那既然SCSS就能做到的东西,我们还有这个所谓的CSS自定义变量干什么呢?人家自然有它的独到之处

  • 1、比如可以在运行时改写,具备动态性;
  • 2、比如方便使用JS读取和改写;
  • 3、比如可继承、可组合、同时具有作用域。

# 声明变量

返回顶部

语法很简单,分成两步,声明变量使用变量

声明变量

使用的是--前缀。

:root{
    --*: xxxx;
    /* --variety-name: variety-value; */
}
1
2
3
4

这里需要注意几个点:

  • :root匹配的就是HTML中的<html>元素,具有最高的权重,:root声明的变量就是全局变量
  • CSS自定义变量对大小写敏感,--color和--Color是两个变量;
  • 变量必须声明在{}中,如果在这里把它理解为属性就更好记忆了,毕竟我们不会把CSS属性写到括号外边去;
  • *号代表的就是我们给变量起的名字。起名字这事真的很烦人,还好CSS变量的名称限制很少,除了一些特殊关键字符不能使用,正常来说你用数字/字母/下划线_/短横线-都是没问题的,据说还可以使用中文、日文和韩文。
:root{
    --黑色:#000;
}

body{
    background: var(--黑色);
}
1
2
3
4
5
6
7

# 使用变量

返回顶部

使用变量

很简单,就是我们很熟悉的var关键字。

/* 定义变量 */
:root{
    --*: #000;
    /* 例如 --color-bg: #000; */
}

/* 使用变量 */
body{
    background: var(--*);
    /* 例如 background: var(--color-bg); */
}
1
2
3
4
5
6
7
8
9
10
11

设置默认值

还有一种设置默认值的使用,就是在变量名称后面,加上一个默认值。
也就是说,当这个变量没有被声明过的话,就会使用默认值,不至于没着没落的。

.div{
    background: var(--变量名称,[默认值]);
    /* 例如 background: var(--color-bg, #000); */
}
1
2
3
4

WARNING

注意这里的情况是变量没有被声明过,要是变量是声明过的,但是使用起来是不合法的,那么就会采用原来属性的缺省默认值,并不是后面这个你设定的默认值。

p{
    background-color: var(--color, #000);
    /* --color没有声明过,所以这里的p元素背景颜色时候用了默认值#000 */
}
1
2
3
4
div{
    --color: 20px;
    background-color: var(--color, #000);
    /* 很明显,background-color: 20px;是有语法错误的,所以这里div的背景色为透明,取的是这个属性的默认值 */
}
1
2
3
4
5

# 作用域和权重

返回顶部

  • 1、如果你需要定义一个全局的变量,那么可以放在:root根元素下面
  • 2、如果只需要在部分元素/组件下使用,就定义在相关的类下面
  • 3、另外还可以在@media媒体查询中或者:hover等伪类中使用。

理解了作用域,那么权重也是同样的道理,因为CSS自定义变量是可以继承的,所以权重跟我们平时的属性权重理解是一样的。

:root{
    --color: purple;
}
div{
    --color: green;
}
#alert{
    --color: red;
}
*{
    color: var(--color);
}
1
2
3
4
5
6
7
8
9
10
11
12
<p>请问我是什么颜色</p>
<div>请问我是什么颜色</div>
<div id="alert">
    请问我是什么颜色
    <p>请问我是什么颜色</p>
    <p style="--color: grey;">请问我是什么颜色</p>
</div>
1
2
3
4
5
6
7

168f0655a974b98b.png

# 变量的其他组合

返回顶部

除了上述的一些用法,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";
    /* 但是字符串的拼接是可以实现的 */
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

变量不止可以直接使用,直接或者通过计算把值传递给另一个变量也是可行的。

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);
    /* 通过计算后传递 */
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 在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);
1
2
3
4
5
6

# 主题色

返回顶部

: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;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

这种需求在CSS预处理器中无法实现一个没有复制代码的方案,总是需要覆盖实现的值和规则,这也经常会导致CSS冗余。
使用CSS自定义属性,解决方案是尽可能的简洁,也避免复制和粘贴代码,因为只要重新定义变量的值,不需要去覆盖一次样式。

第二种方式中两个方式的区别则是,预处理器中变量的作用域是无法继承的,而CSS自定义变量则相对灵活,这样一旦接入方多了之后,两种方式的代码量就会有质的区别。

:root{
    --THEME-COLOR: #fff;
}
button{
    background: var(--THEME-COLOR);
}
a{
    color: var(--THEME-COLOR);
}
1
2
3
4
5
6
7
8
9
<label class="input-label">
    <input class="input-color" type="color" value="color" onchange="changeColor(this.value)">请选择主题颜色
</label>
1
2
3
function changeColor(value){
  document.documentElement.style.setProperty('--THEME-COLOR', value);
}
1
2
3

# 主题色处理

  • 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)));
}
1
2
3
4
5
6
7
8
9
10
// css自定义变量赋值
function changeColor(value){
    const rgb = colorRgb(value);
    document.documentElement.style.setProperty('--COLOR-R', rgb[0]);
    document.documentElement.style.setProperty('--COLOR-G', rgb[1]);
    document.documentElement.style.setProperty('--COLOR-B', rgb[2]);
}

// colorToRgb
function colorRgb(value){
    var sColor = value.toLowerCase();
    //十六进制颜色值的正则表达式
    var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
    // 如果是16进制颜色
    if (sColor && reg.test(sColor)) {
        if (sColor.length === 4) {
            var sColorNew = "#";
            for (var i=1; i<4; i+=1) {
                sColorNew += sColor.slice(i, i+1).concat(sColor.slice(i, i+1));
            }
            sColor = sColorNew;
        }
        //处理六位的颜色值
        var sColorChange = [];
        for (var i=1; i<7; i+=2) {
            sColorChange.push(parseInt("0x"+sColor.slice(i, i+2)));
        }
        return sColorChange;
    }
    return sColor;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  • HSL

HSL的H(hue)分量,代表的是人眼所能感知的颜色范围,这些颜色分布在一个平面的色相环上,取值范围是0°到360°的圆心角,每个角度可以代表一种颜色。
HSL的S(saturation)分量,指的是色彩的饱和度,它用0%至100%的值描述了相同色相、明度下色彩纯度的变化。数值越大,颜色中的灰色越少,颜色越鲜艳,呈现一种从理性(灰度)到感性(纯色)的变化。
HSL的L(lightness)分量,指的是色彩的明度,作用是控制色彩的明暗变化。它同样使用了0%至100%的取值范围。数值越小,色彩越暗,越接近于黑色;数值越大,色彩越亮,越接近于白色。

: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%));
}
1
2
3
4
5
6
7
8
9
function changeColor(value){
  const rgb = value.colorRgb();
  const hsl = rgbToHsl(rgb[0], rgb[1], rgb[2]);
  document.documentElement.style.setProperty('--COLOR-H', hsl[0]);
  document.documentElement.style.setProperty('--COLOR-S', hsl[1]);
  document.documentElement.style.setProperty('--COLOR-L', hsl[2]);
}

function rgbToHsl(r, g, b) {
    r /= 255, g /= 255, b /= 255;
    var max = Math.max(r, g, b), min = Math.min(r, g, b);
    var h, s, l = (max + min) / 2;

    if (max == min){
        h = s = 0; // achromatic
    } else {
        var d = max - min;
        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
        switch(max) {
            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
            case g: h = (b - r) / d + 2; break;
            case b: h = (r - g) / d + 4; break;
        }
        h /= 6;
    }
  
    h = Math.floor(h * 360);
    s = Math.floor(s * 100);
    l = Math.floor(l * 100);

    return [h, s, l];
}

String.prototype.colorRgb = function(){
    var sColor = this.toLowerCase();
    //十六进制颜色值的正则表达式
    var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
    // 如果是16进制颜色
    if (sColor && reg.test(sColor)) {
        if (sColor.length === 4) {
            var sColorNew = "#";
            for (var i=1; i<4; i+=1) {
                sColorNew += sColor.slice(i, i+1).concat(sColor.slice(i, i+1));
            }
            sColor = sColorNew;
        }
        //处理六位的颜色值
        var sColorChange = [];
        for (var i=1; i<7; i+=2) {
            sColorChange.push(parseInt("0x"+sColor.slice(i, i+2)));
        }
        return sColorChange;
    }
    return sColor;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
  • 遮罩改变颜色
.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);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function changeColor(value){
  document.documentElement.style.setProperty('--THEME-COLOR', value);
}
1
2
3

# 注意事项

返回顶部

  • 在一些浏览器中,针对CSS变量的复杂calc()运算可能不能工作;
  • 进行calc()运算时,最好能提供默认值:calc(var(--base-line-height, 0) * 1rem);
  • 不能作为媒体查询值使用:
@media screen and (min-width: var(--desktop-breakpoint) ) {
};
1
2
  • 图片地址,如url( var(--image-url) ) ,不会生效;
  • 因为CSS自定义变量对大小写敏感,故建议全局变量使用全大写形式,除了设置整体主题色,尽量减少改动全局变量;
  • web端可将主题颜色等变量设置在根元素html,并通过上述方法修改自定义属性的值;
  • 小程序因无法获取DOM,无法直接修改CSS自定义属性的值,可以采取在页面的最外层元素设置行内样式的方式重置自定义属性的值,如:
.container{
    --THEME-COLOR: #f00;
}
1
2
3
<view class="container" style="--THEME-COLOR: #0f0;">
    <!-- 该结构下的元素,重置为行内样式的主题色 -->
</view>
1
2
3