浅析前端一键换肤5种方案:css样式覆盖、实现多套css主题、css自定义变量实现、webpack-theme-color-replacer插件实现自定义主题色、UI框架自定义主题功能
一、css 样式覆盖实现
1、核心:通过切换 CSS 选择器的方式实现主题样式的切换
- 在组件中保留不变的样式,将需要变化的样式进行抽离
- 提供多种样式,给不同的主题定义一个对应的 CSS 选择器
- 根据不同主题设置不同的样式
2、如何实现:
(1)通过 vuex
存储和控制全局的主题色;
(2)在 template
模板中通过 vuex
中的主题设置对应类名
(3)比如 theme.css
中通过 .light
和 .dark
两个类选择器来区分明亮主题和暗黑主题,并且事先准备了它们对应的样式
这种方式比较简单,就不多说。
3、缺点:
多种主题样式都要引入,代码量大;样式不易管理,需要改的话,几个主题样式里都需要改;样式不易查找,导致开发效率低等
二、实现多套 CSS 主题样式
1、核心:实现多套 CSS 主题样式,根据用户切换操作,通过 link
标签动态加载不同的主题样式,主要解决了多个主题色被编译到一个文件中导致单个文件过大的问题
PS:在很多给代码设置样式的时候,都可以见到这种不同主题样式的情景
2、实现:
(1)css 部分直接拆分成 ligth.css
和 dark.css
两个文件
(2)设置主题部分的 setTheme.js
代码如下
export default function setTheme(theme = 'ligth') {
let link = document.querySelector('#theme-link')
let href = "/theme/" + theme + ".css"
if (!link) {
let head = document.querySelector('head')
link = document.createElement('link')
link.id = '#theme-link'
link.rel = "stylesheet"
link.href = href
head.appendChild(link)
} else {
link.href = href
}
}
3、缺点:其实跟第一种差不多,与第一种方案相比,这种解决了多个主题色被编译到一个文件,导致文件过大问题。但是这种方式又没法把“变”与“不变”的样式分开,比如有些不需要主题色的样式,其实是“不变的”,应该不需要在每个 css 文件中都写一套的。
4、这样其实演化出进化版本:
(1)不变的 css 作为一个文件,如:common.css
(2)根据主题色变化的 css 作为多个主题色文件,如:light.css 和 dark.css
三、css 变量实现
// 无UI库依赖的主题切换
// 核心思想:css3 中的 :root 伪类选择器和 var 变量的应用
// 1、定义主题变量
:root {
--theme-color: #ccc;
}
// 2、使用主题变量
.test{
color: var(--theme-color);
}
// 3、动态改变主题
document.documentElement.style.setProperty('--theme-color', '#fff');
1、核心:通过 body.style.setProperty(key, value)
动态修改 body 上的 CSS 变量,使得页面上的其他部分可以应用最新的 CSS 变量对应的样式。
2、实现:
(1)比如 theme.css 中负责定义全局的 CSS 变量
/* 实现方式一 */
:root {
--theme-bg: initial; // 背景色
--theme-color: initial; // 字体色
--theme-boder-color: initial; // 边框色
}
/* 实现方式二 */
/* 默认值:light */
:root {
--theme-bg: #fff;
--theme-color: rgb(51, 50, 50);
--theme-img-bg: #fff;
--theme-boder-color: #d6d6d6;
}
/* 暗黑:dark */
[data-theme='dark'] {
--theme-bg: rgb(51, 50, 50);
--theme-color: #fff;
--theme-boder-color: #fff;
}
这里的实现方式一,就是通过 setProperty() 方法用于设置一个新的 CSS 属性,同时也可以修改 CSS 声明块中已存在的属性。
实现方式二,就是通过局部css变量覆盖全局css变量的原理
(2)比如 themeUtil.js 中负责获取当前对应样式值,以及设置 body 上的 CSS 变量值,如
const darkTheme = 'rgb(51, 50, 50)'
const lightTheme = '#fff'
const lightBorderTheme = '#d6d6d6'
// 获取对应的主题色值
export const getThemeMap = (isLight) => {
return { // 这里其实可以通过 key value 去设置多种主题的样式
'theme-bg': isLight ? lightTheme : darkTheme,
'theme-color': isLight ? darkTheme : lightTheme,
'theme-boder-color': isLight ? lightBorderTheme : lightTheme,
}
}
// 设置主题色值
export const setTheme = (isLight = true) => {
const themeMap = getThemeMap(isLight)
const body = document.body
/* 实现方式一 */ 设置全局的css变量
Object.keys(themeMap).forEach(key => {
body.style.setProperty(`--${key}`, themeMap[key])
})
/* 实现方式二 */ 设置局部的css变量,局部的会覆盖全局的变量
// body.setAttribute('data-theme', isLight ? 'light' : 'dark')
}
(3)通过 var()
在组件中应用对应 CSS 变量,比如在头部中的使用:color: var(--theme-color);
3、缺点:兼容性不好
4、优化:可通过 css-vars-ponyfill
对 CSS 变量进行兼容处理
四、webpack-theme-color-replacer插件实现自定义主题色
以上方案都是我们需要事先知道有哪些主题色方案,但是,如果主题不固定的,怎么办呢?
也有方案实现:可借用webpack插件:webpack-theme-color-replacer 来实现,但我没具体用过,想了解的搜一下吧,网上挺多介绍如何做的。
五、UI 框架自定义主题功能
比如 ElementUI 的自定义主题,具体见官方文档:https://element.eleme.cn/#/zh-CN/component/custom-theme
Ant-design-vue 定制主题,具体见官方文档:https://www.antdv.com/docs/vue/customize-theme-cn/