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