webpack下的动态主题实现方案

webpack下的动态主题实现方案

2022年09月23日 阅读:11 字数:901 阅读时长:2 分钟

基于ant-design-vue for vue2版本,构建工具使用webpack 5的一个项目需要实现主题切换功能的方案

前言

基于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;
}

推荐阅读

恰饭区

评论区 0

0/500

还没有评论,快来抢第一吧