「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。
根据vue-element-admin实现的sass自定义主题颜色切换
原理
| 匹配主色调相关颜色,更改style中的cssString,或生成新的cssString到自定义的style标签
- 将方法封装到Vuex
- 颜色处理器 chroma.js
- 淡色/暗色生成 Tint & Shade Generator
不使用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,
}
扩展
- 使用:export获取sass中的主题色变量
- 将主题色存储到localStorage或cookie
总结
为什么使用Vuex封装且使用Promise&try{...}catch(e){...}:
- 实时获取/设置主题色
- setPrimary()方法中可定制更多操作