CSS变量 的 秘密

无意间翻刷了CSS Day 2022上Lea Verou 介绍 CSS Variable 的视频, 不得不感慨CSS已经快变成我不认识的样子了。结合视频中的几个案例聊聊CSS变量。

1 初识CSS变量

如果你之前使用过诸如Less, Sass的预编译的CSS扩展,那你一定对其中的变量系统有印象,比如Sass使用$声明变量:

$base-color: #c6538c;
$border-dark: rgba($base-color, 0.88);

1.1 CSS变量的定义

其实在原生的CSS中也可以使用--来声明变量,如--color: pink。声明完成之后可以通过var()来调用对应的变量,一个简单的例子如下:

button {
  --color: green;
  border: 1px solid var(--color);
  color: var(--color);
  /* 其他不重要的装饰样式 略*/
}

在这个例子里面,我们通过--color定义了color 变量为绿色,并通过var(--color)将该值应用到按钮的边框和颜色上,看到的结果如下:

image

通过var()调用CSS变量的时候,我们也可以指定一个备选值,如果变量不存在的话,CSS就会调用这个备选值。比如在下面的案例中,我们尝试调用没有声明的变量--color2,并指定备选颜色是橙色:

#button_fallback {
  border: 1px solid var(--color2, orange);
  color: var(--color2, orange);
}

最终结果显示的就是备选的橙色

image

1.2 通过JS修改CSS变量

当然我们也可以通过JS去设置CSS变量,即通过style.setProperty()方法来设置css变量,比如我们每次在按钮被点击的时候随机设置一种颜色:

<button onclick="this.style.setProperty('--color', `hsl(${Math.random() * 360} 90% 50%)`)">Click me</button>

那么,他运行起来的效果就是这样:

image

1.3 CSS变量的变量类型 与 动画

CSS有了变量,为什么还需要定义变量类型呢?这是多此一举么?我们可以通过一个动画的例子来说明这个问题。

在这个例子中我们给一个按钮(id="button_example4")设置一个5s的颜色改变的动画,并在动画中修改颜色的hue值从0到180。

@keyframes color-hue {
  from { --color-hue: 0; }
  to { --color-hue: 180; }
}

#button_example4{
  --color: hsl(var(--color-hue) 60% 50%);
  animation: 5s color-hue linear infinite;
}

按照我们的预想,这个动画应该会在5s内从颜色A渐变到颜色B,但结果却是:

image

颜色并没有渐变,而是每5s颜色直接跳跃成另外一种颜色。这是由于我们并没有指定color-hue变量的类型,CSS并不知道color-hue应该是数值还是文本类型的值(如red,blue等)。

1.3.1 通过@property定义CSS变量的类型

如果想要声明变量的类型,我们可以通过@property来定义,在上面的例子中中,我们加入对--color-hue的类型声明:

@property --color-hue {
    syntax: "<number>";
    initial-value: 0;
    inherits: true;
}

这里声明它的类型syntax是数值<number>,初始值initial-value0,我们再来看一下效果:

image

完美的渐变过程!

除了<number>以外,我们也可以使用其他的类型,比如通过以下的方式来指定<color>类型的值:

@property --color  {
    syntax: "<color>";
    initial-value: black;
    inherits: true;
}

除了在CSS中定义变量的类型,我们也可以通过JS来的CSS.registerProperty() API来定义,比如:

window.CSS.registerProperty({
  name: '--color',
  syntax: '<color>',
  inherits: false,
  initialValue: black,
});

完整的例子:

2 CSS变量的魔力

2.1 利用CSS变量的运行时检查来制作样式的开关

我一直觉得下面的例子很神奇(交互Demo在本节底部)。我们可以像使用JS一样,通过调用ONOFF变量来控制按钮是否使用高光效果 - 当我们把glossy设置为OFF的时候,高光效果消失,反之亦然:

image

是不是很有意思?这主要运用了 CSS运行时检查 的一个hack,全名叫 Invalid At Computed Value Time (IACVT)。 顾名思义,当CSS发现变量值为不可用的值的时候,会忽略当前内容并将该条的CSS设置成unset状态 - 即无效状态。下面的一些情况都会触发IACVT。

  1. 值类型不正确

    --foo: 42deg;
    background: var(--foo);

    这里我们虽然定义了变量foo,但是由于background的属性不能为角度,所以最终结果是background: unset;

  2. 未初始化或者值为空()的变量

    background: var(--foo);
    --foo: ;
    background: var(--foo);

    以上两种情况,都会导致background无效即background: unset;

  3. 引用了不可用的值

    --foo: var(--bar);
    --bar: var(--foo);

    循环引用,但是--foo和--bar均未被正确的赋值,所以两个的结果都会变成 unset

知道了以上内容,再结合var()的fallback机制 - 当var的第一个参数为guaranteed-invalid值的时候并且我们提供了第二个参数,var会调用第二个fallback的参数。我们就可以制作成带有 ONOFF 的CSS功能了:

  1. 首先我们定义 ONOFF

    button {
      --ON: initial;
      --OFF: ;
    }

    这里我们使用initial初始化ON并用空白字符串把OFF定义为无效的CSS。

  2. 利用var()的fallback配置border, backgrond

    button {
        border: var(--glossy, .05em solid black);
        background: var(--glossy, linear-gradient(hsl(0 0% 100% / .4), transparent)) var(--_color);
        box-shadow: var(--glossy, 0 .1em hsl(0 0% 100% / .4) inset);
        line-height: calc(1.5 var(--glossy, - .4));
    }

    由于initial是一个guaranteed-invalid值,当var的第一个参数为guaranteed-invalid的时候,var会去调用fallback的值。所以当我们把glossy设置成ON的时候,border就变成了var(initial, .05em solid black)自然应用的就是.05em solid black了,但是当我们把glossy设置为OFF的时候,由于空值是一个有效值valid (empty) value,所以 border: 就不会起作用。同理对于line-height也成立,当glossyON的时候line-height就是1.5 - .4,当glossyOFF的时候该条样式不生效

体验Demo:

3. 逆天的CSS变量

我只能用逆天来形容接下来看到的Demo(Demo在本章节的底部)- 通过一个变量p 就可以控制柱状图的高度、文本数值以及背景颜色,而且全程没有用到一行JS

ScreenFlow

这个案例中我觉得有以下几点比较有意思:

  1. CSS变量控制柱状条的高度
  2. CSS变量控制文本显示(如何处理单位符合%,如何做到四舍五入)
  3. CSS变量控制柱状条的背景颜色

我们一个一个看过来:

3.1 CSS变量控制柱状条的高度

这应该是一道送分题,忽略基础样式和布局,如果我们使用百分比来显示柱状图的高度的话,那只要将对应的数值转换成百分比的形式即可,如:20->20%18.4->18.4%

如何实现呢?脑海里第一个跳出来的是直接拼接可以么,比如 hight: var(--p)%,答案是不行的。不过好在CSS的计算函数calc可以提供数值->数值单位的转换,我们只要*对应的1个单位的值即可:

height: calc(var(--p) * 1%);

当然如果需要转换成其他的单位,也可以直接乘,比如转换成px: height: calc(var(--p) * 1px);

顺便提及一下,现阶段从数值转换成单位是很容易的,但是想要从单位转换成数值(如100% -> 100),还并不支持。

3.2 CSS变量控制文本显示

百分比的文本我们是通过 div::beforecontent来实现的,但content并不接受类似于高度的直接转换 calc(var(--p) * 1%) 也不支持直接调用变量,如content: var(--p) "%";。所以我们想到可以通过counter-reset来变相的解决我们的问题,于是就有了:

.bar-chart > div::before {
    counter-reset: p var(--p);
    content: counter(p) "%";
}

但是仔细观察,我没发现如果p是小数的话,结果居然是0% image

这是为什么呢?

有没有可能是和变量类型相关?

那我们尝试手动声明p是数值

@property --integer {
    syntax: "<integer>";
    initial-value: 0;
    inherits: true;
}

.bar-chart > div::before {
    --integer: calc(var(--p));
    counter-reset: p var(--integer);
    content: counter(p) "%";
}

Bingo! 问题解决了!

这里如果我们细看的话,你会发现CSS在转换小数到整数的时候,用的是类似JS的round方法。如果我们需要类似ceil或者floor的话,我们可以在calc中去+ 0.5或者- 0.5,如--integer: calc(var(--p) + 0.5 ); 这里注意符号(+/-)前后要保留空格。

3.3 CSS变量控制背景颜色

背景颜色是一个变化区间,对于所有在数值区域范围内的变化我们都可以通过一个公式来实现 Start + (End - Start) * percent,这里我们使用的是hsl颜色,计划是将h控制在 50 - 100 的范围内,l控制在 50% - 40%的范围。套用上面的公式,很容易得到下面的CSS

--h: calc(50 + (190 - 50) * var(--p) / 100); /* 50 to 190 */
--l: calc(50% + (40% - 50%) * var(--p) / 100); /* 50% to 40% */
background: hsl(var(--h) 100% var(--l));

体验Demo:

52230
  • logo
    • HI, THERE!I AM MOFEI

      (C) 2010-2024 Code & Design by Mofei

      Powered by Dufing (2010-2020) & Next.js

      IPC证:沪ICP备2022019571号-1