动态换肤

主题定制能帮助开发者快速搭建不同主题风格的视觉稿, 节省一定视觉效果开发成本, 但在大量项目实战过程中发现, 公司产品对动态换肤的需求已经越来越明显. 在经过充分的项目落地实践之后, 总结以下动态换肤方案供开发者参考讨论.

引入组件样式

// main.js

import WinDesign from 'win-design'
import 'win-design/lib/themes/default.css' // 注意, 一定是编译后的css文件

WARNING

此方案是参考element-ui早期主题换肤方案及开源社区其他换肤方案后总结而成. 目前该方案已在“流行病学史调查”项目(该项目的后端管理系统由win-design开发, 实现了在一套前端项目服务中支撑100+需求方的主题定制需求)中得到充分的验证, 开发者可以放心选用.

添加动态换肤基本功能

开发者可以根据自己的业务需求, 将该代码融合进业务逻辑中. 也可以采用mixin的方式将以下代码进行混入处理, 便于管理. 该方案主要思想: 业务场景中维护theme 变量, 通过watch监听theme的变化, 进而触发切换主题色的功能函数.

onThemeChange函数的主要思想是选取网站头部的style标签文件, 通过相关主题色值对比进行强制内容更换, 以达到换肤的目的(略粗暴, 但好用 😛 ).

// 组件库主题色初始值
const DEFAULT_THEME = '#0F49ED'

export default {
  data() {
    return {
      theme: DEFAULT_THEME
    };
  },
  methods: {
    /**
     * 更新样式
     * @param {String} style style文件innerText
     * @param {Array} preCluster 旧的主题色集
     * @param {Array} curCluster 新的主题色集
     */
    updateStyle(style, preCluster, curCluster) {
      let newStyle = style

      preCluster.forEach((color, index) => {
        newStyle = newStyle.replace(new RegExp(color, 'ig'), curCluster[index])
      })

      return newStyle
    },
    /**
     * 获取主题色相关颜色集合
     * @param {String} theme 主题色值
     */
    getThemeCluster(theme) {
      const tintColor = (color, tint) => {
        let red = parseInt(color.slice(0, 2), 16),
          green = parseInt(color.slice(2, 4), 16),
          blue = parseInt(color.slice(4, 6), 16)

        if (tint === 0) return [red, green, blue].join(',')

        red += Math.round(tint * (255 - red))
        green += Math.round(tint * (255 - green))
        blue += Math.round(tint * (255 - blue))
        red = red.toString(16)
        green = green.toString(16)
        blue = blue.toString(16)

        return `#${red}${green}${blue}`
      }

      const shadeColor = (color, shade) => {
        let red = parseInt(color.slice(0, 2), 16),
          green = parseInt(color.slice(2, 4), 16),
          blue = parseInt(color.slice(4, 6), 16)

        red = Math.round((1 - shade) * red)
        green = Math.round((1 - shade) * green)
        blue = Math.round((1 - shade) * blue)
        red = red.toString(16)
        green = green.toString(16)
        blue = blue.toString(16)

        return `#${red}${green}${blue}`
      }

      const clusters = [theme]

      for (let i = 0; i <= 9; i += 1) {
        const cluster = tintColor(theme, Number((i / 10).toFixed(2)))
        clusters.push(cluster)
      }

      clusters.push(shadeColor(theme, 0.1))

      return clusters
    },
    /**
     * 主题色变化时的事件回调
     * @param {String} newValue 新主题色
     * @param {String} oldValue 旧主题色
     */
    onThemeChange(newValue, oldValue) {
      const curCluster = this.getThemeCluster(newValue.replace('#', '')),
        preCluster = this.getThemeCluster(oldValue.replace('#', '')),
        styles = [].slice.call(document.querySelectorAll('style'))

      styles.forEach(style => {
        const { innerText } = style

        if (typeof innerText !== 'string') return

        style.innerText = this.updateStyle(
          innerText,
          preCluster,
          curCluster
        )
      })
    }
  },
  watch: {
    theme(curColor, preColor) {
      // 监听theme值变化, 更新style内容以达到换肤目的
      // ps: theme可直接看作主题切换组件的v-model使用
      this.onThemeChange(curColor, preColor)
    }
  }
}

修改webpack配置

使用vue-cli搭建项目的开发者需要注意一点, 由于vue脚手架对css样式存在一个优化选项, 导致项目编译后样式文件以link的方式存在, 从而使得本方案从源头上失效, 要想解决该问题, 需要调整一下webpack的配置.

// vue.config.js

module.exports = {
  css: {
    extract: false,
  }
};
上次更新: 12/14/2020, 11:06:11 AM