CSS封装: CSS自定义属性的应用

很多人已经接触过CSS自定义属性(CSS Custom Properties)了,但是很多人对于它的使用,也仅停留在对于全局变量的定义,用于规范页面的整体风格,例如:

:root {
    --main-color: #369;
}

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

但比起自定义CSS属性,如果项目中使用了预编译CSS(例如scss),一些人更倾向于使用其自带的变量系统,其语法更为简练:

$mainColor: #369;

p {
    color: $mainColor;
}

但这些预编译CSS最终都会被编译成原生CSS,而这些所谓的变量也不过是单纯的复制粘贴为其定义的值,并不会实际起到变量的作用。

CSS自定义属性预编译CSS的变量不同,它们实际上就是一个变量,这个变量可以被 JavaScriptCSS AnimationCSS Transition 所控制。

制作一个简单的进度条

先从一个简单的案例开始说起,例如我们需要制作一个进度条,如下图所示:

图片[1] - CSS封装: CSS自定义属性的应用 - 君霖驿站

首先,我们定义一个HTML元素figure,并且给它一个class名为progress-bar

<figure class="progress-bar"></figure>

对于CSS,我们暂时先将它的基础框架搭建出来:

.progress-bar {
    position: relative;
    
    display: flex;
    align-items: center;
    justify-content: center;
    
    width: 80vw;
    height: 4rem;
    
    border-radius: 5rem;
    border: solid 2px #333;
    background-color: #888;
    
    overflow: hidden;
}

.progress-bar::before {
    content: '';
    display: block;
    background-color: orangered;
    position: absolute;
    top: 0;
    left: 0;
    width: 50%;
    height: 100%;
}

.progress-bar::after {
    content: '50%';
    z-index: 1;
    color: white;
    font-size: 1.25em;
}

这些都是一些基础的CSS,不作过度阐述,大致可以解释为如下:

  • 使用了 flex 布局让其所有元素居中。
  • 使用了伪元素 ::before 负责渲染进度条的填充部分,其 width50%,也就是一半,背景颜色暂定为 orangered,动态颜色我们之后再解决。
  • 使用了伪元素 ::after 进行渲染进度条的文字部分,内容暂时强行指定为 '50%'

此时我们的进度条长这样:

图片[2] - CSS封装: CSS自定义属性的应用 - 君霖驿站

目前而言,我们的进度条目前只停留在 50%。我们希望只用一个变量去控制进度条所有的行为,包括其填充的长度以及颜色

这时候CSS自定义属性就派上用场了,我们暂且将它称之为--progress

.progress-bar {
    --progress: 0.25;
    
    /* ... */
}

我们规定它将是一个 01 之间的值:

  • 当它的值为 0 时,进度条是空的,颜色为红色
  • 而当它的值为 1 时,进度条将是满的,并且颜色为绿色
  • 而当它的值在 01 以内时,进度条将填充对应的区间,并且颜色为红色到绿色之间

控制进度条长度

为了让 --progress 可以控制进度条的长度,我们将 ::beforewidth 改写为:

.progress-bar::before {
    /* ... */
    width: calc(var(--progress) * 100%);
    /* ... */
}
  • 所有需要用到CSS自定义属性的地方,一律用var()进行包裹,否则CSS将其无法识别。
  • var() 可以用于计算 calc() 方法中。
  • 如果变量没有单位(即纯数字),用它乘以任何一个带单位的属性结果会获得其单位。

因为我们的 width 是一个百分比值,而 --progress 只是一个单纯的数字,我们需要使用 calc 乘以 100% 将其转化为对应的百分比值。

我们在 .progress-bar 中定义了 --progress 属性值为 0.25,所以计算结果应该为 width: 25%;,其实际效果如下:

图片[3] - CSS封装: CSS自定义属性的应用 - 君霖驿站

大致目测了一下,应该是 25% 没错。至于目前的文字还是显示 50%,我们稍后再解决。

这时如果你去改动 --progress 的值,进度条的长度也会进行相应的变化,此时进度条的长度算是解决了。

控制进度条颜色

首先考虑一下,我们希望--progress0 时,颜色应该为红色;--progress1 时,颜色应该为绿色。在介于 01 之间的值,它的颜色应该也介于红色与绿色之间。

这时候,如果使用 RGB 去控制颜色的话,效果不会很理想,因为我们很难用一个值去控制三个颜色所对应的值,计算起来也异常头疼。

这里我推荐大家使用 HSL 进行颜色控制:

  • H(Hue): 色相
  • S(Saturation): 饱和度
  • L(Lightness): 亮度

我们可以想象一下,我们所需要的颜色的饱和度亮度应该是不变的,变的只有其色相(绿),如果我们可以将色相--progress去控制,不就刚好解决了我们的需求了吗?

对于HSL颜色控制,大家可以去搜索一下其具体用法,这不是本篇文章讨论的重点,这里只进行简单的介绍。

  • H 是一个角度值,区间为 0deg360deg 之间。
  • H0deg 时,颜色为红色。
  • H120deg 时(三分之一)时,颜色为绿色。
  • H240deg 时(三分之二)时,颜色为蓝色。
  • S 是一个百分比值,0% 为无饱和度,100% 为最大饱和度。
  • L 是一个百分比值,0% 为黑色,100% 为白色,只有介于两者之间才能看到色相和对比度。

所以我们可以得知我们需要 H 的值就是在 0deg120deg 之间,这时我们套入一个公式,将 0 ~ 1 转化为 0deg ~ 120deg

.progress-bar::before {
    /* ... */
    background-color: hsl(calc(0deg + var(--progress) * 120deg), 100%, 35%);
    /* ... */
}

此时我们对比一下,当 --progress0.250.50.75时的状态:

图片[4] - CSS封装: CSS自定义属性的应用 - 君霖驿站

这样我们的颜色就算是控制完成了,此这时如果你去改动 --progress 的值,进度条的长度和颜色都会进行相应的变化。

控制其他属性

我们如法炮制,使用 --progress 去控制 borderbox-shadow等属性,这样我们的进度条就和本篇文章刚开始的效果并无二致了。

完整CSS代码:

.progress-bar {
    --progress: 0.25;
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 80vw;
    height: 4rem;
    border-radius: 5rem;
    border: solid 2px hsl(calc((30 + var(--progress) * 60) * 1deg), 100%, 15%);
    box-shadow: 0px 0px 2rem hsla(calc((30 + var(--progress) * 60) * 1deg), 100%, 50%, var(--progress));
    background-color: #888;
    overflow: hidden;
}

.progress-bar::before {
    content: '';
    display: block;
    background-color: hsl(calc(0deg + var(--progress) * 120deg), 100%, 50%);
    position: absolute;
    top: 0;
    left: 0;
    width: calc(var(--progress) * 100%);
    height: 100%;
}

.progress-bar::after {
    content: '50%';
    z-index: 1;
    color: white;
    font-size: 1.25em;
}
复制代码

至此我们实现了仅用一个css属性值 --progress 控制了该元素的各种属性值,一个简单的CSS属性封装就完成了。

控制文字显示

很遗憾,CSS自定义属性并不能控制伪元素的content,你会发现如下写法并没有什么卵用:

.progress-bar::after {
    content: calc(var(--progress) * 100) '%';
    /* ... */
}
复制代码

这是因为 content 只接受字符串以及 attr() ,而CSS并没有将值转化为字符串的方法,所以我们只能从JavaScript下手了。

题外话,希望CSS X的到来可以修复此问题。

我们规定一个HTML Attribute,名为 data-progress,然后在使用JavaScript设置其CSS自定义属性 --progress 时,我们也一并设置其 data-progress

//html
<figure class="progress-bar" data-progress="50"></figure>

//css
.progress-bar::after {
    content: attr(data-progress) '%';
    /* ... */
}

//js
[...document.querySelectorAll('.progress-bar')].forEach(e => e.addEventListener('mousemove', ({offsetX, target}) => {
    // progress,0到1之间的值
    const progress = offsetX / target.clientWidth;
    // 设置CSS自定义属性
    target.style.setProperty('--progress', progress);
    // 设置Attribute
    target.setAttribute('data-progress', Math.abs((progress * 100)).toFixed(0));
}));

我们使用了鼠标位置可以控制 --progressdata-progress,这样一来我们就可以实时观察进度条的状态了:

图片[5] - CSS封装: CSS自定义属性的应用 - 君霖驿站

*使用CSS Houdini给CSS自定义属性添加动画

如果你希望使用 transition 或者 animation 去给 --progress 添加动画,你可以在js中定义如下:

CSS.registerProperty({
    name: '--progress',
    syntax: '<number>',
    inherits: false,
    initialValue: '0'
});

如此一来浏览器便知道 --progress 是一个 number,可以正确地解析如下 keyframes,否则默认只能从 0 直接跳为 1,反之亦然。

@keyframes load {
    from {
        --progress: 0;
    }
    
    to {
        --progress: 1;
    }
}

鉴于CSS Houdini目前支持率和稳定性并不是特别好,这里也只做一下简单的概述,如果有兴趣请查阅相关文献。

© 版权声明
THE END
喜欢就支持一下吧
点赞0赞赏
分享
评论 抢沙发
jurieo的头像 - 君霖驿站

昵称

取消
昵称表情代码图片