element-plus如何更换主题

1,503 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

前言

后台系统中经常涉及到主题变更的功能,本文记录如何实现主题变更的功能

实现原理大致分为四步

  1. 获取当前element-plus的所有样式
  2. 定义要替换后的样式
  3. 在原样式中用新样式替换
  4. 把替换后的样式写入style标签

功能实现

功能实现的一些基础

首先需要一个变量表示默认主色

从vuex中获取当前保存的主色

// 默认色值
const mColor = ref(store.getters.mainColor)

每次更换主色,需要我们把更新后的主色保存在本地存储中

// vuex theme.js模块

// 获取本地存储 设置本地存储
import { getItem, setItem } from '@/utils/storage'
// 定义的常量 主题色保存的key 和默认色值
import { MAIN_COLOR, DEFAULT_COLOR } from '@/constant'
export default {
  namespaced: true,
  state: () => ({
    mainColor: getItem(MAIN_COLOR) || DEFAULT_COLOR
  }),
  mutations: {
    /**
     * 设置主题色
     */
    setMainColor(state, newColor) {
      state.mainColor = newColor
      setItem(MAIN_COLOR, newColor)
    }
  }
}

在确定更换主色后调用setMainColor,并传入新的主色

const comfirm = async () => {
  // 保存最新的主题色
  store.commit('theme/setMainColor', mColor.value)
  // 关闭 dialog
  closed()
}

更换主题

新建工具类 theme.js

根据实现原理,先实现第一步

1. 获取当前element-plus的所有样式

在element-plus官网中,能找到所有css样式的地址

样式位置.png 复制之后在浏览器打开

所有样式.png 以上便是所有的默认样式

我们可以直接使用axios请求该地址,但是需要注意版本号的问题,我们访问的应该是我们使用的element-plus的版本号,所以需要得到目前使用的版本号

// 获取当前element-plus的默认样式表
const getOriginalStyle = async () => {
  // 获取版本号
  const version = require('element-plus/package.json').version
  const url = `https://unpkg.com/element-plus@${version}/dist/index.css`
  //请求到所有的默认样式
  const { data } = await axios(url)
  return data
}

2. 定义替换后的样式

我们需要根据更换的主色生成最新的色值表,这个功能需要安装两个插件

rgb-hex: 转换rgb(a)颜色为十六进制

css-color-function: 在CSS中提出的颜色函数的解析器和转换器

点击了解rgb-hex

点击了解css-color-function

此外还需要一个颜色转化器formula.json,我们改变主色primary后,其对应的颜色会发生变化

{
  "shade-1": "color(primary shade(10%))",
  "light-1": "color(primary tint(10%))",
  "light-2": "color(primary tint(20%))",
  "light-3": "color(primary tint(30%))",
  "light-4": "color(primary tint(40%))",
  "light-5": "color(primary tint(50%))",
  "light-6": "color(primary tint(60%))",
  "light-7": "color(primary tint(70%))",
  "light-8": "color(primary tint(80%))",
  "light-9": "color(primary tint(90%))",
  "subMenuHover": "color(primary tint(70%))",
  "subMenuBg": "color(primary tint(80%))",
  "menuHover": "color(primary tint(90%))",
  "menuBg": "color(primary)"
}

准备完成,开始根据主色生成色值表

我们需要遍历颜色转化器,获取所有key值,然后使用传入的最新的主色值替换原有的primary,再将其转化为十六进制

// 传入主色值
export const generateColors = (primary) => {
  if (!primary) {
    return
  }
  const colors = {
    primary
  }
  // 遍历所有key值
  Object.keys(formula).forEach((key) => {
    const value = formula[key].replace(/primary/g, primary)
    // 转化rgb颜色为十六进制
    colors[key] = '#' + rgbHex(color.convert(value))
  })
  return colors
}

最后生成的色值表为下图,值已经变成了十六进制

色值表.png

3. 把需要替换的色值标记

下方colorMap定义了需要打标记的色值,打标记的意思就是将element-plus默认样式为colorMap的键的颜色替换为colorMap对应的键值

// 把需要替换的色值打上标记
// data为element-plus默认样式
const getStyleTemplate = (data) => {
  // element-plus 默认色值
  const colorMap = {
    '#3a8ee6': 'shade-1',
    '#409eff': 'primary',
    '#53a8ff': 'light-1',
    '#66b1ff': 'light-2',
    '#79bbff': 'light-3',
    '#8cc5ff': 'light-4',
    '#a0cfff': 'light-5',
    '#b3d8ff': 'light-6',
    '#c6e2ff': 'light-7',
    '#d9ecff': 'light-8',
    '#ecf5ff': 'light-9'
  }
  Object.keys(colorMap).forEach((key) => {
    const value = colorMap[key]
    data = data.replace(new RegExp(key, 'ig'), value)
  })
  return data
}

替换前

1.png

替换后

2.png

在上文获取element-plus默认样式表的方法中 直接调用getOriginalStyle,此时我们得到的就是打过标记的默认样式表,接下来等待全局替换

// 获取当前element-plus的默认样式表
const getOriginalStyle = async () => {
  const version = require('element-plus/package.json').version
  const url = `https://unpkg.com/element-plus@${version}/dist/index.css`
  const { data } = await axios(url)
  
  return getStyleTemplate(data)
}

4. 遍历生成的色值表,在默认样式表全局替换

先看打上标记后的样式表

2.png

此处我们需要遍历最新的色值表colors,将cssText中的标记light-1,light-2等等全部替换为色值表中对应的值

整合之前的代码

/*
 *根据主题色 生成最新的样式表
 */
export const generateNewStyle = async (parimaryColor) => {
  // 1. 根据主色生成色值表
  const colors = generateColors(parimaryColor)

  // 2. 获取当前默认样式表,并且把需要替换的色值打上标记
  let cssText = await getOriginalStyle()

  // 3. 遍历生成的色值表,在默认样式表全局替换
  Object.keys(colors).forEach((key) => {
    cssText = cssText.replace(
      new RegExp('(:|\\s+)' + key, 'g'),
      '$1' + colors[key]
    )
  })
  return cssText
}

替换为新的样式后的cssText

3.png

5. 将最新的cssText写入style即可

// 把生成的样式表写入style
export const writeNewStyle = (elNewStyle) => {
  const style = document.createElement('style')
  style.innerText = elNewStyle
  document.head.appendChild(style)
}

6. 修改confirm方法

点击修改主色后,我们需要调用generateNewStylewriteNewStyle方法,更换主题

/**
 * 确定
 * 1. 修改主题色
 * 2. 保存最新的主题色
 * 3. 关闭 dialog
 */
const comfirm = async () => {
  // 1.1 获取主题色
  const newStyleText = await generateNewStyle(mColor.value)
  // 1.2 写入最新主题色
  writeNewStyle(newStyleText)
  // 2. 保存最新的主题色
  store.commit('theme/setMainColor', mColor.value)
  // 3. 关闭 dialog
  closed()
}