Vue2组件库主题色切换实现

本篇技术报告摘录于本人2023年工作技术总结,主要探讨在前端网页实现多主题色切换,技术方案探究和实际公司项目中的运用。

需求背景

在今年的项目中,根据客户的需要,需要在原本亮色的主题下,适配暗色主题,并提供可拓展的空间。当前需要改造的是基于新一代架构的配电房系统和综合能源系统,技术栈为Vue2,使用的组件库为bootstrap Vue,以及少量的element UI和 AntD Vue组件,涉及的组件库较多,改造较复杂。

方案研究

方案1:使用CSS3的CSS变量特性。在不同的作用域下定义浅色和暗黑两套不同的CSS变量,通过JS控制html根部的class属性来切换作用域,以达到切换主题色目的。经兼容性评估,该CSS变量特性兼容至chrome49+,即2017年后的主流浏览器均已支持该特性,可用于生产实践中。当前主流的组件库都开始采用这种方式和来动态更新主题,但较老的组件库因为使用了大量的less或scss颜色函数预处理,不支持CSS变量编译,若直接替换定义的颜色为CSS变量 会导致在编译阶段报错,因此只能手动去书写多套样式,需要大量工作量。

方案2:预设多份less或sass变量文件,使用webpack构建提前将所有的样式编译出总的多份css文件,通过切换 css 文件到达目的,但是需要对项目的所有的 less或sass 的引用模式作调整,对构建环境也需很大的调整,样式与 js 完全分离,不能友好地对组件库的的 less 或 sass 按需编译。

实践方案

经过网上方案的进一步研究,已经有@zougt/some-loader-utils@zougt/theme-css-extract-webpack-plugin两个工具插件,结合方案1和方案2,利用插件来辅助编译出两套css主题代码。实践过程大体分为以下几个步骤:

3.1 定义CSS变量,替换自定义组件和页面中的固定的颜色值

由于项目主要使用scss为css预编译语言,我们主要以scss语法来定义css变量,采用之前的方案1,我们创建一个variable.scss文件,分别定义浅色和深色作用域下的变量:

:root {
  --theme-color: #1b85ff;   //主题色,用于按钮、文字、链接、图标等
  --warning-color: #f9ac30;  //警告提示色,用于警告、提醒等
  // …
}
.dark {
  --theme-color: #6cecff; // 主题色,用于按钮、文字、链接、图标等
  --warning-color: #fcd451; // 警告提示色,用于警告、提醒等
    // …
}

我们对整个项目里所有原有固定写死的色彩CSS属性逐一检查,将其替换为CSS变量。例如,Vbox组件中,CSS将原本固定写死的title颜色#1b85ff,修改为动态的var(--theme-color),那么在默认作用域下颜色为#1b85ff.dark作用域下为#6cecff

.title {
    color: var(--theme-color);
}

3.2 使用JS动态切换全局样式

此时,若根html DOM上无额外类名,浅色主题生效,整体页面风格为浅色主题;若在根html DOM上添加类名.dark,此时暗色CSS变量便会生效,整体页面风格会切换为暗色主题。

我们可以使用JS来控制这个类名的存在,并使用vuex来管理和存储持久化当前的主题模式状态。当点击切换主题按钮时,我们修改vuex中存储的主题模式并切换DOM上的类名,伪代码如下:

 function changeTheme() {
    // 反转Vuex里存放的主题状态值
    if (store.state.system.theme === 'dark') {
      store.commit(MUTATION.SET_THEME, 'light')
    } else {
      store.commit(MUTATION.SET_THEME, 'dark')
    }
    // 在html DOM 上添加主题色类名
    document.documentElement.className = store.state.system.theme
}

3.3 BootstrapVue、AntD组件库的适配

完成了自定义组件的适配,我们还需要对第三方组件库进行适配,BootstrapVue、AntD原生均不支持主题色动态切换,但它们都使用了scss预处理器去定义了主题颜色,因此我们可以通过定义两套变量,在打包过程中生成两套不同作用域下的CSS编译产物,实现主题色切换。

这里我们使用@zougt/some-loader-utils这个插件来帮助我们生成编译产物,使用@zougt/theme-css-extract-webpack-plugin来分离出独立的主题 css 文件,以节省加载时间:

插件的配置方法如下,编辑vue.config.js配置文件

const { getSass } = require('@zougt/some-loader-utils')
const ThemeCssExtractWebpackPlugin = require('@zougt/theme-css-extract-webpack-plugin')

// 定义两套主题
const multipleScopeVars = [
  {
    scopeName: 'light',
    path: path.resolve(__dirname, 'src/assets/styles/theme/light.scss'), // 浅色主题变量文件
  },
  {
    scopeName: 'dark',
    path: path.resolve(__dirname, 'src/assets/styles/theme/dark.scss'), // 深色主题变量文件
  },
]


module.exports = defineConfig({
  chainWebpack: config => {
    // 使用插件,在打包时将两种主题颜色分别打包进不同的css文件,以实现按需加载主题文件,节约首屏加载时间
    config.plugin('ThemeCssExtractWebpackPlugin').use(ThemeCssExtractWebpackPlugin, [
      {
        multipleScopeVars,
        extract,
        outputDir: extractCssOutputDir,
      },
    ])
    },
    css: {
      loaderOptions: {
        sass: {
            // 使用插件,来预处理主题变量文件
          implementation: getSass({ 
            getMultipleScopeVars: () => multipleScopeVars, 
          }),
        },
      }
    }
  })

我们的编译插件就配置好了,剩下的就是需要逐一调整颜色变量。这一步需要一些耐心,我们需要阅读组件库源码,找到编译后颜色在源码中所对应的scss变量,并分别在dark.scss和light.scss文件中将其替换掉。例如,我们想修改Bootstrap Vue中 Button组件的字体颜色,通过阅读源码发现,字体颜色使用了$body-color这个变量,那么我们在主题配色文件中分别将这个变量覆盖定义:

.btn {
  //...
  color: $body-color; // 找到了源码中按钮颜色的来源,是这个$body-color变量定义的
  // ...
}

在浅色主题的light.scss文件中,我们添加一行代码,将这个颜色定义为黑色:

$body-color: #212529;

在深色主题的dark.scss文件中,我们添加一行代码,将这个颜色定义为白色:

$body-color: #fff;

这样,当我们编译后查看打包产物,会发现打包出两份不同作用域下对按钮字体颜色的定义:

.light .btn {
  color: #212529;
}

.dark .btn{
    color: #fff;
}

这样,和3.2步骤中实现主题切换同理,只需控制根组件html DOM树上的class 为light 或是dark,就可以实现主题色的切换。

3.4 ElementUI 组件库的适配

当前使用的BootstrapVue组件库和ElementUI使用了scss 预处理方案,而AntdV使用了less预处理方案,也就意味着我们项目还需要安装less 预处理器,引入AntdV的less文件而不是原先的css,使用编译方式生成AntdV组件样式,这样我们获得了进一步对主题色修改的空间。

由于@zougt/some-loader-utils插件的局限性,无法同时预处理scss和less,无法像3.3步骤那样对less分别定义两套变量文件进行覆盖,但实际发现ElementUI并未使用太多颜色函数,所以我们直接将less变量定义覆盖为CSS变量即可,实践方案类似于3.1和3.3步骤的结合:

@import '~ant-design-vue/dist/antd.less'; // 引入官方提供的 less 样式入口文件

// 修改antd的主题变量
@text-color: var(--subtitle-color);
@tree-showline-icon-color: var(--subtitle-color);
// ...

实践总结

在完成多主题色切换的实践过程中,我们采用了CSS变量和覆盖SCSS或Less变量编译生成两套css文件两种方案的结合,并借助一些插件工具来简化我们手动编码的过程。

JavaScript前端开发

手写一个Promise

2022-1-9 11:31:49

服务器运维系统运维

便宜国外主机汇总

2019-1-21 12:38:40

搜索