换肤功能是一项普遍的需求,尤其是在夜晚,用户更倾向于使用暗黑模式。在我负责的公司项目中,每个项目都有换肤功能的需求。

过去,我主要使用 SCSS 变量,并利用其提供的函数,如 @eachmap-get来实现换肤功能。但因其使用成本高,只能适用于SCSS项目,于是后来我改用 CSS 变量来实现换肤。这样无论是基于 LESS 的 React 项目,还是基于 SCSS 的 Vue 项目,都能应用换肤功能。并且使用时只需调用var函数,降低了使用成本。

Demo地址:github.com/cwjbjy/vite…

1. 前置知识

CSS变量:声明自定义CSS属性,它包含的值可以在整个文档中重复使用。属性名需要以两个减号(—)开始,属性值则可以是任何有效的 CSS 值

 1--fontColor:'#fff'

Var函数:用于使用CSS变量。第一个参数为CSS变量名称,第二个可选参数作为默认值

 1color: var(--fontColor);

CSS属性选择器:匹配具有特定属性或属性值的元素。例如[data-theme=‘black’],将选择所有 data-theme 属性值为 ‘black’ 的元素

2. 定义主题色

1. 新建src/assets/theme/theme-default.css

这里定义字体颜色与布局的背景色,更多CSS变量可根据项目的需求来定义

 1[data-theme='default'] {
 2  
 3  --font-primary: #fff;
 4  --font-highlight: #434a50;
 5  
 6  --background-header: #2f3542;
 7  --background-aside: #545c64;
 8  --background-main: #0678be;
 9}

2. 新建src/assets/theme/theme-black.css

再定义一套暗黑主题色

 1[data-theme='black'] {
 2  
 3  --font-primary: #fff;
 4  --font-highlight: #434a50;
 5  
 6  --background-header: #303030;
 7  --background-aside: #303030;
 8  --background-main: #393939;
 9}

3. 新建src/assets/theme/index.css

在index.css文件中导出全部主题色

 1@import './theme-default.css'; 
 2@import './theme-black.css';

4. 引入全局样式

在入口文件引入样式,比如我这里是main.tsx

 1import '@/assets/styles/theme/index.css';

3. 在html标签上增加自定义属性

修改index.html,在html标签上增加自定义属性data-theme

 1<html lang="en" data-theme="default"></html>

这里使用data-theme是为了被CSS属性选择器[data-theme=‘default’]选中,也可更换为其他自定义属性,只需与CSS属性选择器对应上即可。

4. 修改CSS主题色

关键点:监听change事件,使用document.documentElement.setAttribute动态修改data-theme属性,然后CSS属性选择器将自动选择对应的css变量

 1<template>
 2  <div>
 3    <select name="pets" @change="handleChange">
 4      <option value="default">默认色</option>
 5      <option value="black">黑色</option>
 6    </select>
 7    <div>登录页面</div>
 8  </div>
 9</template>
10
11<script setup lang="ts">
12const handleChange = (e: Event) => {
13  window.document.documentElement.setAttribute('data-theme', (e.target as HTMLSelectElement).value);
14};
15</script>
16
17<style lang="scss">
18body {
19  color: var(--font-primary);
20  background-color: var(--background-main);
21}
22</style>

效果图,默认色:

效果图,暗黑色:

5. 修改JS主题色

切换主题色,除了需要修改css样式,有时也需在js文件中修改样式,例如修改echarts的配置文件,来改变柱状图、饼图等的颜色。

1. 新建src/config/theme.js

定义图像的颜色,这里定义字体的颜色,默认情况下字体为黑色,暗黑模式下,字体为白色

 1const themeColor = {
 2  default: {
 3    font: '#333',
 4  },
 5  black: {
 6    font: '#fff',
 7  },
 8};
 9
10export default themeColor;

2. 修改vue文件

关键点:

  1. 定义主题色TS类型,规定默认和暗黑两种:type ThemeTypes = 'default' | 'black';

  2. 定义theme响应式变量,用来记录当前主题色:const theme = ref<ThemeTypes>('default');

  3. 监听change事件,将选中的值赋给theme:theme.value = selectTheme;

  4. 使用watch进行监听,如果theme改变,则重新绘制echarts图形

完整的vue文件:

 1<template>
 2  <div>
 3    <select name="pets" @change="handleChange">
 4      <option value="default">默认色</option>
 5      <option value="black">黑色</option>
 6    </select>
 7    <div>登录页面</div>
 8    <div ref="echartRef" class="myChart"></div>
 9  </div>
10</template>
11
12<script setup lang="ts">
13import { onMounted, ref, watch } from 'vue';
14import themeColor from '@/config/theme';
15import * as echarts from 'echarts';
16
17type ThemeTypes = 'default' | 'black';
18
19const echartRef = ref<HTMLDivElement | null>(null);
20const theme = ref<ThemeTypes>('default');
21const handleChange = (e: Event) => {
22  const selectTheme = (e.target as HTMLSelectElement).value as ThemeTypes;
23  theme.value = selectTheme;
24  window.document.documentElement.setAttribute('data-theme', selectTheme);
25};
26
27const drawGraph = () => {
28  let echartsInstance = echarts.getInstanceByDom(echartRef.value!);
29  if (!echartsInstance) {
30    echartsInstance = echarts.init(echartRef.value);
31  }
32  echartsInstance.clear();
33  var option = {
34    color: ['#3398DB'],
35    title: {
36      text: '柱状图',
37      left: 'center',
38      textStyle: {
39        color: themeColor[theme.value].font,
40      },
41    },
42    xAxis: [
43      {
44        type: 'category',
45        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
46        axisLabel: {
47          show: true,
48          color: themeColor[theme.value].font,
49        },
50        nameTextStyle: {
51          color: themeColor[theme.value].font,
52        },
53      },
54    ],
55    yAxis: [
56      {
57        type: 'value',
58        axisLabel: {
59          show: true,
60          color: themeColor[theme.value].font,
61        },
62        nameTextStyle: {
63          color: themeColor[theme.value].font,
64        },
65      },
66    ],
67    series: [
68      {
69        name: '直接访问',
70        type: 'bar',
71        barWidth: '60%',
72        data: [10, 52, 200, 334, 390, 330, 220],
73      },
74    ],
75  };
76
77  echartsInstance.setOption(option);
78};
79onMounted(() => {
80  drawGraph();
81});
82watch(theme, () => {
83  drawGraph();
84});
85</script>
86
87<style lang="scss">
88body {
89  color: var(--font-primary);
90  background-color: var(--background-main);
91}
92.myChart {
93  width: 300px;
94  height: 300px;
95}
96</style>

在特殊的日子里,网页有整体变灰色的需求。可以使用filter 的 grayscale() 改变图像灰度,值在 0% 到 100% 之间,值为0%展示原图,值为100% 则完全转为灰度图像

 1body {
 2  filter: grayscale(1); 
 3}

本文只是介绍大概的思路,更多的功能可根据业务增加。例如将主题色theme存储到pinia上,应用到全局上;将主题色存储到localStorage上,在页面刷新时,防止主题色恢复默认。

本文可结合以下文章阅读:

  1. Vite4.3+Typescript+Vue3+Pinia 最新搭建企业级前端项目

  2. Vue3.3.1+TS 全新使用指南

  3. pinia(2.0.34) 最新使用指南与持久化缓存

如果有更多的换肤方案,欢迎在留言区留言讨论。我会根据留言区内容实时更新。

个人笔记记录 2021 ~ 2025