妙用CSS变量,让你的CSS变得更心动
作者: JowayYoung
仓库: Github 、 CodePen
博客: 官网 、 掘金 、 思否 、 知乎
公众号: IQ前端
特别声明:原创不易,未经授权不得转载或抄袭,如需转载可联系笔者授权
前言
CSS变量 又叫 CSS自定义属性 ,为什么会突然提起这个很少人用到的东西呢?因为最近在重构个人官网,不知道为什么突然喜欢用上 CSS变量 ,可能其自身隐藏的魅力,让笔者对它刮目相看。
谈到为什么会在CSS中使用变量,下面举个栗子,估计大家一看就会明白。
/* 不使用CSS变量 */
.title {
background-color: red;
.desc {
background-color: red;
/* 使用CSS变量 */
:root {
--bg-color: red;
.title {
background-color: var(--bg-color);
.desc {
background-color: var(--bg-color);
}
看完可能会觉得使用
CSS变量
的代码量多了一点,但是有没有想到突然某天万恶的策划小哥哥和设计小姐姐说要做一个换肤功能。按照平常的思路,估计有些同学就会按照
默认颜色主题
增加一份对照的
新颜色主题
CSS文件。这样每次新增需求都同时维护几套主题颜色多麻烦啊。
此时 CSS变量 就派上用场了,提前跟设计小姐姐规范好各种需要变换的颜色并通过 CSS变量 进行定义,通过JS批量操作这些定义好的 CSS变量 即可。这也是 变换主题颜色 的一种解决方案之一,好处在于只需写一套CSS代码。
["red", "blue", "green"].forEach(v => {
const btn = document.getElementById(`${v}-theme-btn`);
btn.addEventListener("click", () => document.body.style.setProperty("--bg-color", v));
在此总结下CSS使用变量的好处:
- 减少样式代码的重复性
- 增加样式代码的扩展性
- 提高样式代码的灵活性
- 增多一种CSS与JS的通讯方式
- 不用深层遍历DOM改变某个样式
可能有些同学会问,Sass和Less早就实现了变量这个特性,何必再多此一举呢。可是细想一下, CSS变量 对比Sass和Less的变量,又有它的过人之处。
- 浏览器原生特性,无需经过任何转译就可直接运行
- DOM对象一员,极大便利了CSS与JS之间的联系
认识
本来打算用一半篇幅讲述 CSS变量 的规范和用法,但是网上一搜一大把就感觉没必要了,贴上阮一峰老师写的教程 《CSS变量教程》 。同时笔者也对 CSS变量 的细节地方进行一个整理,方便大家记忆。
-
声明:
--变量名
-
读取:
var(--变量名, 默认值)
- 类型
-
普通:只能用作
属性值
不能用作属性名
-
字符:与字符串拼接
"Hello, "var(--name)
-
数值:使用
calc()
与数值单位连用var(--width) * 10px
- 作用域
-
范围:在
当前元素块作用域
及其子元素块作用域
下有效 -
优先级别:
内联样式 > ID选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器
接下来使用几个特别的场景展示 CSS变量 的魅力。还是那句话, 一样东西有使用的场景,那自然就会有它的价值 ,那么用的人也会越来越多。
使用场景
其实 CSS变量 有一个特别好用的场景,那就是结合List元素集合使用。如果不明白这是什么,请继续往下看。
以下所有演示代码基于vue文件,但HTML、CSS和JS分开书写,为了简化CSS的书写而使用Sass进行预处理,方便代码演示
条形加载条
一个条形加载条通常由几条线条组成,并且每条线条对应一个存在不同时延的相同动画,通过时间差运行相同的动画,从而产生加载效果。估计大部分的同学可能会把CSS代码写成以下这样。
<ul class="strip-loading flex-ct-x">
<li v-for="v in 6" :key="v"></li>
.loading {
width: 200px;
height: 200px;
border-radius: 3px;
width: 6px;
height: 30px;
background-color: #f66;
animation: beat 1s ease-in-out infinite;
& + li {
margin-left: 5px;
&:nth-child(2) {
animation-delay: 200ms;
&:nth-child(3) {
animation-delay: 400ms;
&:nth-child(4) {
animation-delay: 600ms;
&:nth-child(5) {
animation-delay: 800ms;
&:nth-child(6) {
animation-delay: 1s;
}
分析代码发现,每个
<li>
只是存在
animation-delay
不同,而其余代码则完全相同,换成其他类似的List元素集合场景,那岂不是有10个
<li>
就写10个
:nth-child
。
显然这种方法不灵活也不容易封装成组件,如果能像JS那样封装成一个函数,并根据参数输出不同的样式效果,那就更棒了。说到这里,很明显就是为了铺垫 CSS变量 的开发技巧了。
对于HTML部分的修改,让每个
<li>
拥有一个自己作用域下的
CSS变量
。对于CSS部分的修改,就需要分析哪些属性是随着
index
递增而发生规律变化的,对规律变化的部分使用
CSS变量
表达式代替即可。
<ul class="strip-loading flex-ct-x">
<li v-for="v in 6" :key="v" :style="`--line-index: ${v}`"></li>
.strip-loading {
width: 200px;
height: 200px;
--time: calc((var(--line-index) - 1) * 200ms);
border-radius: 3px;
width: 6px;
height: 30px;
background-color: #f66;
animation: beat 1.5s ease-in-out var(--time) infinite;
& + li {
margin-left: 5px;
源码链接可在文章结尾处获取
代码中的变量
--line-index
和
--time
使每个
<li>
拥有一个属于自己的作用域。例如第2个
<li>
,
--line-index
的值为2,
--time
的计算值为
200ms
,换成第3个
<li>
后这两个值又会不同了。
这就是
CSS变量
的作用范围所致(
在当前元素块作用域及其子元素块作用域下有效
),因此在
.strip-loading
的块作用域下调用
--line-index
是无效的。
/* flex属性无效 */
.loading {
display: flex;
align-items: center;
flex: var(--line-index);
}
通过妙用
CSS变量
,也把CSS代码从
29行
缩减到
15行
,对于那些含有List元素集合越多的场景,效果就更明显。而且这样写也更加美观更加容易维护,某天说加载效果的时间差不明显,直接将
calc((var(--line-index) - 1) * 200ms)
里的
200ms
调整成
400ms
即可。就无需对每个
:nth-child(n)
进行修改了。
心形加载条
前段时间刷掘金看到
陈大鱼头兄
的心形加载条,觉得挺漂亮的,很带感觉。
通过动图分析,发现每条线条的背景色和动画时延不一致,另外动画运行时的高度也不一致。细心的你可能还会发现,第1条和第9条的高度一致,第2条和第8条的高度一致,依次类推,得到
高度变换相同类
的公式:
对称index = 总数 + 1 - index
。
背景色使用了滤镜的色相旋转
hue-rotate
函数,目的是为了使颜色过渡得更加自然;动画时延的设置和上面
条形加载条
的设置一致。下面就用
CSS变量
根据看到的动图实现一番。
<div class="heart-loading flex-ct-x">
<ul style="--line-count: 9;">
<li v-for="v in 9" :key="v" :class="`line-${v}`" :style="`--line-index: ${v}`"></li>
</div>
.heart-loading {
width: 200px;
height: 200px;
display: flex;
justify-content: space-between;
width: 150px;
height: 10px;
--Θ: calc(var(--line-index) / var(--line-count) * .5turn);
--time: calc((var(--line-index) - 1) * 40ms);
border-radius: 5px;
width: 10px;
height: 10px;
background-color: #3c9;
filter: hue-rotate(var(--Θ));
animation-duration: 1s;
animation-delay: var(--time);
animation-iteration-count: infinite;
.line-1,
.line-9 {
animation-name: line-move-1;
.line-2,
.line-8 {
animation-name: line-move-2;
.line-3,
.line-7 {
animation-name: line-move-3;
.line-4,
.line-6 {
animation-name: line-move-4;
.line-5 {
animation-name: line-move-5;
源码链接可在文章结尾处获取
一波操作后就有了下面的效果。和
陈大鱼头兄
的心形加载条对比一下,颜色、波动曲线和跳动频率有点不一样,在暖色调的蔓延和肾上腺素的飙升下,这是一种心动的感觉。想起自己曾经写的一首诗:
我见犹怜,爱不释手,雅俗共赏,君子好逑
。
标签导航栏
上面通过两个加载条演示了 CSS变量 在CSS中的运用以及一些妙用技巧,现在通过标签导航栏演示 CSS变量 在JS中的运用。
JS中主要有3个操作 CSS变量 的API,看上去简单易记,分别如下:
-
读取变量:
elem.style.getPropertyValue()
-
设置变量:
elem.style.setProperty()
-
删除变量:
elem.style.removeProperty()
先上效果图,效果中主要是使用 CSS变量 标记每个Tab的背景色和切换Tab的显示状态。
<div class="tab-navbar">
<a v-for="(v, i) in list" :key="v" :class="{ active: index === i }" @click="select(i)">标题{{i + 1}}</a>
</nav>
<ul ref="tabs" :style="`--tab-count: ${list.length}`">
<li v-for="(v, i) in list" :key="v" :style="`--bg-color: ${v}`">内容{{i + 1}}</li>
</div>
</div>
.tab-navbar {
display: flex;
overflow: hidden;
flex-direction: column-reverse;
border-radius: 10px;
width: 300px;
height: 400px;
nav {
display: flex;
height: 40px;
background-color: #f0f0f0;
line-height: 40px;
text-align: center;
flex: 1;
cursor: pointer;
transition: all 300ms;
&.active {
background-color: #66f;
font-weight: bold;
color: #fff;
div {
flex: 1;
--tab-index: 0;
--tab-width: calc(var(--tab-count) * 100%);
--tab-move: calc(var(--tab-index) / var(--tab-count) * -100%);
display: flex;
flex-wrap: nowrap;
width: var(--tab-width);
height: 100%;
transform: translate3d(var(--tab-move), 0, 0);
transition: all 300ms;
display: flex;
justify-content: center;
align-items: center;
flex: 1;
background-color: var(--bg-color);
font-weight: bold;
font-size: 20px;
color: #fff;
export default {
data() {
return {
index: 0,
list: ["#f66", "#09f", "#3c9"]
methods: {
select(i) {
this.index = i;
this.$refs.tabs.style.setProperty("--tab-index", i);
源码链接可在文章结尾处获取
在
<ul>
上定义
--tab-index
表示Tab当前的索引,当点击按钮时重置
--tab-index
的值,就可实现不操作DOM来移动
<ul>
的位置显示指定的Tab。不操作DOM而可移动
<ul>
是因为定义了
--tab-move
,通过
calc()
计算
--tab-index
与
--tab-move
的关系,从而操控
transform: translate3d()
来移动
<ul>
。
另外在
<li>
上定义
--bg-color
表示Tab的背景色,也是一种比较简洁的模板赋值方式,总比写
<li :style="backgroundColor: ${color}">
要好看。如果多个CSS属性依赖一个变量赋值,那么使用
CSS变量
赋值到style上就更方便了,那些CSS属性可在CSS文件里进行计算与赋值,这样可帮助JS分担一些属性计算工作。
当然,这个标签导航栏也可通过纯CSS实现,有兴趣的同学可看看笔者之前一篇文章里的 纯CSS标签导航栏 。
悬浮跟踪按钮
通过几个栗子实践了 CSS变量 在CSS和JS上的运用,相信大家已经掌握了其用法和技巧。之前在某个网站看过一个比较酷炫的鼠标悬浮特效,好像也是使用 CSS变量 实现的。笔者凭着记忆也使用 CSS变量 实现一番。
其实思路也比较简单,先对按钮进行布局和着色,然后使用伪元素标记鼠标的位置,定义
--x
和
--y
表示伪元素在按钮里的坐标,通过JS获取鼠标在按钮上的
offsetLeft
和
offsetLeft
分别赋值给
--x
和
--y
,再对伪元素添加径向渐变的背景色,大功告成,一个酷炫的鼠标悬浮跟踪特效就这样诞生了。
<a class="track-btn pr tac" @mousemove="move">
<span>妙用CSS变量,让你的CSS变得更心动</span>
.track-btn {
display: block;
overflow: hidden;
border-radius: 100px;
width: 400px;
height: 50px;
background-color: #66f;
line-height: 50px;
cursor: pointer;
font-weight: bold;
font-size: 18px;
color: #fff;
span {
position: relative;
&::before {
--size: 0;
position: absolute;
left: var(--x);
top: var(--y);
width: var(--size);
height: var(--size);
background-image: radial-gradient(circle closest-side, #09f, transparent);
content: "";
transform: translate3d(-50%, -50%, 0);
transition: all 200ms ease;
&:hover::before {
--size: 400px;
export default {
name: "track-btn",
methods: {
move(e) {
const x = e.pageX - e.target.offsetLeft;
const y = e.pageY - e.target.offsetTop;