CSS变量带你优雅实现前端换肤

552 阅读4分钟

前言

在需求中会遇到一种业务场景,也就是后端下发颜色,前端再根据颜色进行渲染页面的主色调。还有一种场景是用户在前端自行更改主题色,最典型的就是白天/黑夜模式

那我们一般怎么实现呢?最简单的方法就是使用 JS 去动态修改 dom 节点的样式

我们这里用 vue 项目来演示这个代码,设计一个按钮,点击之后修改背景色。演示demo:

// template
<div @click="handleClick">改变颜色</div>

// script
function handleClick() {
  let color = generateRandomColor();
  // 修改颜色相关逻辑
}

function generateRandomColor() {
  const letters = "0123456789ABCDEF";
  let color = "#";
  for (let i = 0; i < 6; i++) {
    color += letters[Math.floor(Math.random() * 16)];
  }
  return color;
}

具体实现

JS 方案

实现

我们这边直接使用 JS 去修改背景色

 let color = generateRandomColor();
 // 修改颜色相关逻辑
 document.body.style.backgroundColor = color

具体的效果如下:

Jul-01-2024 21-36-25.gif

缺点

如果是多个颜色需要变化,要写很多JS代码,代码会很复杂,而且不好维护

CSS变量方案

首先看下什么是 css 变量(自定义属性)。css 变量是由 CSS 作者定义的,它包含的值可以在整个文档中重复使用。通过自定义属性标记设定值(--main-color:black),再通过 var()函数来获取值(color:var(--main-color))

用法

element {
    --main-color: red
}

文档推荐我们的最佳实践是定义在根伪类:root下,这样就可以在 HTML文档的任何地方进行访问了

:root{
    --main-color: red
}

然后我们在某个 DOM 下就可以使用这个变量了

.text {
    color: var(--main-color)
}

如何修改这个变量的值呢?使用 JS 即可

// 如果是 :root 下面
document.body.style.setProperty('--main-color','green')
// 如果是在某个元素下面
element.style.setProperty('--main-color','green')

如何在 JS 中获取这个值?

// 如果是 :root 下面
document.body.style.getProperty('--main-color','green')
// 如果是在某个元素下面
element.style.getProperty('--main-color','green')

实现

我们先在全局定义一个颜色变量

// style.css 中,就是在全局的 css 样式中
:root {
    --bg-color: '#fff'
}

在要更改颜色的地方,使用变量var

body {
  background-color: var(--bg-color);
}

在点击的时候通过 js 修改 --bg-color 的值即可

let color = generateRandomColor();
 // 修改颜色相关逻辑
 document.body.style.setProperty("--bg-color", color);

效果和上面也是一样的

Jul-01-2024 22-06-14.gif

这时候你可能会想,不也是通过 JS 去修改嘛?有什么区别?我直接 JS 一把梭不也好的很。其实还是有区别的,上面你使用 JS 是修改个别 DOM 的值,而这里是修改全局颜色的变量,你修改完全局颜色变量之后,所有用到这个变量的颜色都会改变。

如果你是使用 vue/react 响应式的话,也可以直接修改变量的值,然后通过响应式,去修改用到这个变量的地方,当然也可以。但是你有没有发现,你的数据修改层始终停留在 JS 上,再通过 JS 去修改 CSS层面,我们这个只能称为 JS 变量。

image.png 而我们使用 CSS 变量后,我们就从 JS 维度脱离,而是修改 CSS 维度让 DOM 进行了变化,所以这也是为什么称为 CSS 变量。

image.png

兼容性

兼容性已经比较好了

image.png 如果你要兼容 IE 或者低版本,可以添加 css-vars-ponyfill 来实现

Vue实现

vue3 中有一个CSS 中的 v-bind() ,更加语义化展示了 CSS 变量,我们可以在 JS 层直接下沉到 CSS 层维度去做 CSS 的变化。

实现

// templete
 <div class="text-color"></div>

// script 声明一个变量
const bgcolor = ref("#fff");
function handleClick() {
  let color = generateRandomColor();
  bgcolor.value = color;
}

// style 中直接使用
.text-color {
  width: 100px;
  height: 100px;
  background-color: v-bind(bgcolor);
}

实现的效果如下:

Jul-01-2024 22-37-48.gif

我们可以通过查看这个节点的颜色发现,使用的同样是 CSS 变量

image.png

原理

在组件内声明的变量会通过内联样式的方式添加在这个组件的 dom 节点上,并对变量名进行 hash 处理,然后会在 css 中通过 var 方式去引用这个变量。

局限性

由于 vue 是通过 app 挂载上去的,所以这个变量最多是声明在 #app 这个节点下面的,body 上就应用不上了。

总结

在我们遇到一些比较复杂的需要全局配置的换肤业务场景的时候,不妨试一下 CSS 变量,会让你更加容易维护你的代码,更加优雅