Appearance
Vant 2 主题颜色动态切换方案
为什么要讨论这个需求
事实上本网站已经提供了《IE 下切换主题配色解决方案》,虽然 Vant 2 使用的 CSS 预处理器是 Less,但是也可以套用这个方案,那么既然如此,本章还有什么讨论意义呢?
这里问题在于:
Vant 2 是面向移动端的,2022 年以后,百分之百的移动浏览器都支持CSS Variables
,所以我们应当使用CSS Variables
实现主题切换。
按说面向移动端的 UI 库通常都会使用CSS Variables
技术,但是偏偏 Vant 2 开发较早,没有使用CSS Variables
,依然使用 Less 变量。
Less 变量做不到的方面包括:
无法做到用户随意设置色值。
每个主题必须生成一套 CSS,无法更小地压缩 CSS 大小。
所以我们要找一个更佳的方案。
更佳方案
将 Vant 2 的 Less 文件改造成支持CSS Variables
,然后准备一些命名空间,每个命名空间包含不同 CSS 变量值,最后只需要切换命名空间即可,惬意得很。
事实上,所有的面向移动端却没有使用CSS Variables
的 UI 库都可以考虑使用本方案。
无法使用本文方案的情况
如果某 UI 库的 Sass 或 Less 代码中含有颜色函数,那么这个文件就不能使用本方案,因为颜色函数只能处理具体色值,不能处理 CSS 变量,页面会报错。事实上 Vant 2 的“数字键盘”组件就包含颜色函数。这时候怎么办?
如果涉案组件较少,而且项目恰好用不到,可以按需加载组件,直接避开这些组件就好了。
如果涉案组件较多,而且避不开,这时其实不太好解决:
要么将代码拷出来修改,不再引入那个原版文件,而是引入这个修改版文件。
要么放弃使用该组件,而是使用其他 UI 库的对应组件开替代,当然了,前提是其他 UI 库没有使用颜色函数。
要么直接放弃本文方案。
Vant 2 幸好只有数字键盘组件有颜色函数,所以我们不加载这个组件的样式就可以了。
方案实施
1. 引入 Vant 2 的 Less 文件
要么参看《官方文档》的“定制方法”一节,按需引入。按需引入组件必须先
yarn add -D babel-plugin-import
。要么创建
@/assets/styles/vant-components.less
,模仿官方的index.less
引入样式,范例见下,然后让App.vue
引入这个文件即可。
less
// 注意排除数字键盘组件的 less
@import '../../../node_modules/vant/es/style/base.less';
@import '../../../node_modules/vant/es/overlay/index.less';
@import '../../../node_modules/vant/es/info/index.less';
// ... more
WARNING
无论哪种引入方式,必须只能引入 Less 文件,不能引入 CSS 文件。
2. vue.config.js 里修改颜色变量
本文只演示 2 个变量,其他变量不再演示。
vue.config.js:
js
module.exports = defineConfig({
css: {
loaderOptions: {
less: {
lessOptions: {
modifyVars: {
// Vant 2 将主色认定为蓝色,这其实是一种狭隘的思想,主色可以是任何合适的颜色,不局限于蓝色
// 所以为了规范、清晰起见,我使用 primary 指代原来的 @blue,使用 success 指代 @green
'@blue': 'var(--primary)',
'@green': 'var(--success)',
// Vant 2 的按钮组件的配色中,“主要”对应绿色,“信息”对应蓝色,这是一种过时的理念,
// 而且跟 Tag 等其他组件的规范不一致,
// 好在 Vant 3 中已经改过来,让“主要”对应蓝色,让“成功”对应绿色,取消了“信息”按钮,
// 所以我们在 Vant 2 中也纠正一下:
'@button-primary-background-color': '@blue',
'@button-primary-border-color': '@blue',
'@button-info-background-color': '@green',
'@button-info-border-color': '@green',
// ... 其他变量
},
},
},
},
},
// ... 其他已写的代码
});
3. 准备theme.scss
比如我们的项目是一个教育类项目,包含“家长”和“教师”两种身份,我希望不同身份登录后看到的主题配色也不同。
在@/assets/styles/
下新建theme.scss
文件。为什么我没使用 Less 文件呢?因为脚手架只要安装相关 loader 就可以支持同时使用多个预处理语言,而我更习惯使用 SCSS 文件。
scss
@use 'sass:math';
$theme-maps: (
parent: (
primary: #3388cc,
success: #44aa55,
),
teacher: (
primary: #0088ff,
success: #11aa22,
),
);
body {
@each $theme, $color-maps in $theme-maps {
$primary: map-get($color-maps, primary);
$success: map-get($color-maps, success);
// 有命名空间
&.theme-#{$theme} {
/* 针对 Vant 2 的变量 */
// 在 vue.config.js 设的什么变量名,这里就写什么变量名
// 我只以主色和成功色变量为例,其他变量不再演示
--primary: #{$primary};
--success: #{$success};
/* 下面是 Vant 2 之外的我用到的自定义变量 */
// 变亮 10%
$primary-lighten-10: lighten($primary, 10%);
--primary-lighten-10: #{$primary-lighten-10};
// 变亮 10% 基础上再加透明度
@each $alpha in (10, 20, 30, 40, 50) {
--primary-lighten-10-alpha-#{$alpha}: #{transparentize(
$primary-lighten-10,
math.div($alpha, 100)
)};
}
}
// 无命名空间:某些场合需要同时用到 2 个主题的配色,所以设两个没有命名空间的变量
--#{$theme}-primary: #{$primary};
--#{$theme}-success: #{$success};
}
/* 工具类,便于快速开发 */
.primary {
color: var(--primary);
}
.bg-primary {
background-color: var(--primary);
}
.border-primary {
border-color: var(--primary);
}
}
4. 不同身份使用各自的主题
正确引入上文提到的相关文件。
比如以家长身份登录:登录后立即执行
document.body.className = 'theme-parent';
即可。