前言
基于ant-design-vue for vue2版本,构建工具使用webpack 5的一个项目需要实现主题切换功能
常规方案:
- 依赖 css3 vars,需要UI库支持
- 预编译好几套主题的样式文件
在线动态主题的实现,具有如下特点:
- 使用成本很低
- 跟ui框架无关,Element-ui、iview、Ant-design 等等等(只要基于 less/sass)都可以
- 不依赖 css3 vars
- 浏览器兼容性良好(IE9+ ?)
- 一个主色带动所有梯度色
2、实现方案
使用webpack-theme-color-replacer插件
参考https://github.com/vueComponent/ant-design-vue-pro
2.1、安装依赖
"ant-design-vue": "^1.7.8",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-unit-jest": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"webpack-theme-color-replacer": "^1.3.26"
2.2、插件配置
// build/plugin.config.js
// @see https://github.com/hzsrc/webpack-theme-color-replacer
const generate = require('@ant-design/colors/lib/generate').default
const ThemeColorReplacer = require('webpack-theme-color-replacer')
const themeConfig = require('./themeConfig')
const getAntdSerials = color => {
// 淡化(即less的tint)
const lightens = new Array(9).fill().map((t, i) => {
return ThemeColorReplacer.varyColor.lighten(color, i / 10)
})
const colorPalettes = generate(color)
const rgb = ThemeColorReplacer.varyColor.toNum3(color.replace('#', '')).join(',')
return lightens.concat(colorPalettes).concat(rgb)
}
const themePluginOption = {
fileName: 'css/theme-colors-[contenthash:8].css',
matchColors: getAntdSerials(themeConfig.primaryColor), // 主色系列
// 改变样式选择器,解决样式覆盖问题
changeSelector(selector) {
switch (selector) {
case '.ant-calendar-today .ant-calendar-date':
return ':not(.ant-calendar-selected-date):not(.ant-calendar-selected-day)' + selector
case '.ant-btn:focus,.ant-btn:hover':
return '.ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger),.ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger)'
case '.ant-btn.active,.ant-btn:active':
return '.ant-btn.active:not(.ant-btn-primary):not(.ant-btn-danger),.ant-btn:active:not(.ant-btn-primary):not(.ant-btn-danger)'
case '.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon':
case '.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon':
return ':not(.ant-steps-item-process)' + selector
// fixed https://github.com/vueComponent/ant-design-vue-pro/issues/876
case '.ant-steps-item-process .ant-steps-item-icon':
return ':not(.ant-steps-item-custom)' + selector
case '.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-item-open,.ant-menu-horizontal>.ant-menu-item-selected,.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu-active,.ant-menu-horizontal>.ant-menu-submenu-open,.ant-menu-horizontal>.ant-menu-submenu-selected,.ant-menu-horizontal>.ant-menu-submenu:hover':
case '.ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal > .ant-menu-submenu-selected,.ant-menu-horizontal > .ant-menu-submenu:hover':
return '.ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu:hover'
case '.ant-menu-horizontal > .ant-menu-item-selected > a':
case '.ant-menu-horizontal>.ant-menu-item-selected>a':
return '.ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item-selected > a'
case '.ant-menu-horizontal > .ant-menu-item > a:hover':
case '.ant-menu-horizontal>.ant-menu-item>a:hover':
return '.ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item > a:hover'
default:
return selector
}
}
}
const createThemeColorReplacerPlugin = () => new ThemeColorReplacer(themePluginOption)
module.exports = createThemeColorReplacerPlugin
主题列表
module.exports = {
primaryColor: '#1FCAC4',
theme: [
{
key: '明青(默认)',
color: '#1FCAC4'
},
{
key: '拂晓蓝',
color: '#1890FF'
},
{
key: '极客蓝',
color: '#2F54EB'
}
]
}
vue-cli配置
const { defineConfig } = require('@vue/cli-service')
const createThemeColorReplacerPlugin = require('./config/plugin.config')
const themeConfig = require('./config/themeConfig')
module.exports = defineConfig({
configureWebpack: {
plugins: [
createThemeColorReplacerPlugin()
]
}
})
2.3、在线替换
// utils/themeColor.js
import generate from '@ant-design/colors/lib/generate'
import client from 'webpack-theme-color-replacer/client'
export default {
getAntdSerials(color) {
// 淡化(即less的tint)
const lightens = new Array(9).fill().map((t, i) => {
return client.varyColor.lighten(color, i / 10)
})
// colorPalette变换得到颜色值
const colorPalettes = generate(color)
const rgb = client.varyColor.toNum3(color.replace('#', '')).join(',')
return lightens.concat(colorPalettes).concat(rgb)
},
changeColor(newColor) {
const options = {
newColors: this.getAntdSerials(newColor), // new colors array, one-to-one corresponde with `matchColors`
changeUrl(cssUrl) {
return `/${cssUrl}` // while router is not `hash` mode, it needs absolute path
}
}
return client.changer.changeColor(options, Promise)
}
}
// App.vue
import themeColor from './utils/themeColor.js'
themeColor.changeColor('#66ccff')
3、常见问题
1、生成的css样式缺失,只提取到部分样式规则
matchColors需要写主色以及梯度色,样式里面也要使用主色或主色透明以及梯度色
2、和compression-webpack-plugin插件不兼容
简单来说就算webpack-theme-color-replacer会在有引用的文件里面添加css chunk文件地址、颜色等配置,但是不会添加到compression-webpack-plugin生成的gz文件内,如果访问的是gz文件就会丢失配置。
我的做法是先取消构建时的gzip压缩,改用nginx实现
http {
#开启gzip
gzip on;
gzip_static on;
#低于1kb的资源不压缩
gzip_min_length 1k;
#压缩级别1-9,越大压缩率越高,同时消耗cpu资源也越多,建议设置在5左右。
gzip_comp_level 3;
#需要压缩哪些响应类型的资源,多个空格隔开。不建议压缩图片.
gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;
#配置禁用gzip条件,支持正则。此处表示ie6及以下不启用gzip(因为ie低版本不支持)
gzip_disable "MSIE [1-6]\.";
#是否添加“Vary: Accept-Encoding”响应头
gzip_vary on;
}
还没有评论,快来抢第一吧