根据vue-element-admin实现的sass自定义主题色切换(换肤)

554 阅读2分钟

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。

根据vue-element-admin实现的sass自定义主题颜色切换

原理

| 匹配主色调相关颜色,更改style中的cssString,或生成新的cssString到自定义的style标签

不使用chroma.js生成淡色/暗色?因为chroma.js的颜色混合和element-ui的主题色生成不太匹配,或者需要开发人员自己调试。

代码

import chroma from "chroma-js"

const state = {
    primary: "#409EFF", // 主题色
}

const mutations = {
    SET_PRIMARY: (state, data) => {
        // 建议将颜色存储到cookie或者localStorage
        state.primary = data
    },
}

const actions = {
    setPrimary({ commit, state }, data) {
        const oldVaule = state.primary
        if (!data || 
            !chroma(data) || 
            chroma(data).hex().toLowerCase() === oldValue.toLowerCase()) 
            return
        
        /**
         * 生成淡色
         */
        const rgbTint = (rgb, i) => {
            return [
                rgb[0] + (255 - rgb[0]) * i * 0.1,
                rgb[1] + (255 - rgb[1]) * i * 0.1,
                rgb[2] + (255 - rgb[2]) * i * 0.1,
            ]
        }
        
        /**
         * 生成暗色
         */
        const rgbShade = (rgb, i) => {
            return [
                rgb[0] * (1 - 0.1 * i), 
                rgb[1] * (1 - 0.1 * i), 
                rgb[2] * (1 - 0.1 * i),
            ]
        }
        
        /**
         * 生成 [暗色*10,原色,淡色*10] 数组
         */
        const calculate = color => {
            if (!color || !chroma(color)) return
            const rgb = chroma(color).rgb()
            let tints = [],
                shades = []
            for (var i = 0; i < 10; i++) 
                tints[i] = chroma(rgbTint(rgb, i)).hex().toLowerCase()
            for (var i = 0; i < 10; i++) 
                shades[i] = chroma(rgbShade(rgb, i)).hex().toLowerCase()
            return [...new Set([
                ...shades, 
                ...[chroma(color).hex().toLowerCase()], 
                ...tints
            ])]
        }
        
        /**
         * 返回颜色更新后的cssString
         */
        const updateStyle = (styleText, oldCluster, newCluster) => {
            if (!styleText || !oldCluster || !newCluster) return
            let newStyleText = styleText
            oldCluster.forEach((color, index) => {
                newStyleText = newStyleText
                    .replace(
                        new RegExp(chroma(color).hex(), "igm"), 
                        chroma(newCluster[index]).hex(),
                    )
                    .replace(
                        new RegExp(chroma(color).rgb().join(", "), "igm"),
                        chroma(newCluster[index]).rgb().join(", "),
                    )
            })
            return newStyleText
        }
        
        /**
         * 根据url获取cssString
         */
        const getCssString = url => {
            return new Promise(resolve => {
                const xhr = new XMLHttpRequest()
                xhr.onreadystatechange = () => {
                    if (xhr.readyState === 4 && xhr.status === 200)
                        resolve(xhr.responseText
                            .replace(/@font-face{[^}]+}/, ""))
                }
                xhr.open("GET", url)
                xhr.send()
            })
        }
        
        return new Promise(async (resolve, reject) => {
            try {
                // 更新style标签中的主题色
                Array.from(document.querySelectorAll("style")).forEach(style => {
                    style.innerText = updateStyle(
                        style.innerText, calculate(oldVaule), calculate(data)
                    )
                })

                // 提取link标签需要更改主题色的cssString到styleText
                let styleText = ""
                let links = Array.from(document.querySelectorAll('link[href$=".css"]'))
                for (const link of links) {
                    let text = await getCssString(link.getAttribute(href))
                    if (text &&
                        (new RegExp(chroma(oldVaule).hex(), "igm").test(text) ||
                        new RegExp(chroma(oldValue).rgb().join(", ")).test(text))
                    )
                    styleText += text
                }

                // 更新styleText中的主题色,并提取到自定义的style标签中
                let myThemeStyle = document.getElementById("my_theme__style")
                if (!myThemeStyle) {
                    myThemeStyle = document.createElement("style")
                    myThemeStyle.setAttribute("id", "my_theme__style")
                    document.head.appendChild(myThemeStyle)
                }
                myThemeStyle.innerText = updateStyle(
                    styleText, calculate(oldValue), calculate(data)
                ) || ""

                commit("SET_PRIMARY", chroma(data).hex())
                resolve(chroma(data).hex())
            } catch (error) {
                commit("SET_PRIMARY", oldValue)
                reject(error)
            }
        })
    })
}

export default {
    namespaced: true,
    state,
    mutations,
    actions,
}

扩展

总结

为什么使用Vuex封装且使用Promise&try{...}catch(e){...}

  • 实时获取/设置主题色
  • setPrimary()方法中可定制更多操作

链接

将方法封装到混入

vue-element-admin

chroma.js

Tint & Shade Generator