前端框架&组件换肤方案
前言
相信大家在工作中,经常会遇到主题切换的需求。主题切换虽然不难,但是各种方案的差异较大,往往都会需要伴随着新主题的代码修改,很浪费时间精力。接下来在本篇文章中,将会带你学习如何0修改完成主题方案建设,开始吧~
介绍
本篇文章将会围绕图上这几部分进行介绍~
组件库主题
由于我们项目中使用的组件库是elementui二次开发的,所以组件库切换方案就参考了elementui的。
预备工作
设置vuex的主题颜色存储,以及同步localstorage存储
export default {
namespaced: true,
state: {
// 主题
theme: window.localStorage.getItem('THEME') || '#166bff',
// 布局模式:line竖版,row横版
layoutType: window.localStorage.getItem('LAYOUT_TYPE') || 'row'
},
mutations: {
// 更新主题
SWITCH_THEME(state, theme) {
state.theme = theme
window.localStorage.setItem('THEME', theme)
},
// 修改菜单布局模式
UPDATE_LAYOUT_TYPE(state, val) {
state.layoutType = val
window.localStorage.setItem('LAYOUT_TYPE', val)
}
},
getters: {
// 获取缓存中的主题色
getTheme(state) {
return state.theme
},
// 获取菜单布局模式
getLayoutType(state) {
return state.layoutType
}
}
}
颜色切换组件
在elementui中,有一个themePicker的组件,所以切换颜色我们可以参考此组件。 项目中完成展示如图:
完整组件代码示例:
<template>
<el-color-picker
v-model="theme"
:predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"
class="theme-picker"
popper-class="theme-picker-dropdown"
/>
</template>
<script>
const version = require('element-ui/package.json').version // element-ui version from node_modules
const ORIGINAL_THEME = '#409EFF' // default color
export default {
data() {
return {
chalk: '', // content of theme-chalk css
theme: ''
}
},
computed: {
defaultTheme() {
return this.$store.getters['base/getTheme']
}
},
watch: {
defaultTheme: {
handler: function(val, oldVal) {
this.theme = val
},
immediate: true
},
async theme(val) {
const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
if (typeof val !== 'string') return
const themeCluster = this.getThemeCluster(val.replace('#', ''))
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
console.log(themeCluster, originalCluster)
const $message = this.$message({
message: ' Compiling the theme',
customClass: 'theme-message',
type: 'success',
duration: 0,
iconClass: 'el-icon-loading'
})
const getHandler = (variable, id) => {
return () => {
const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
let styleTag = document.getElementById(id)
if (!styleTag) {
styleTag = document.createElement('style')
styleTag.setAttribute('id', id)
document.head.appendChild(styleTag)
}
styleTag.innerText = newStyle
}
}
if (!this.chalk) {
const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
await this.getCSSString(url, 'chalk')
}
const chalkHandler = getHandler('chalk', 'chalk-style')
chalkHandler()
const styles = [].slice.call(document.querySelectorAll('style'))
.filter(style => {
const text = style.innerText
return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
})
console.log(styles)
styles.forEach(style => {
const { innerText } = style
if (typeof innerText !== 'string') return
style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
})
this.$emit('change', val)
$message.close()
// 刷新重载模板样式
window.location.reload()
}
},
methods: {
updateStyle(style, oldCluster, newCluster) {
let newStyle = style
console.log('ceshi01', newStyle,'old样式',oldCluster,'new样式',newCluster)
oldCluster.forEach((color, index) => {
console.log(color, index)
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
})
return newStyle
},
getCSSString(url, variable) {
return new Promise(resolve => {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
resolve()
}
}
xhr.open('GET', url)
xhr.send()
})
},
getThemeCluster(theme) {
const tintColor = (color, tint) => {
let red = parseInt(color.slice(0, 2), 16)
let green = parseInt(color.slice(2, 4), 16)
let blue = parseInt(color.slice(4, 6), 16)
if (tint === 0) { // when primary color is in its rgb space
return [red, green, blue].join(',')
} else {
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)
let green = parseInt(color.slice(2, 4), 16)
let 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++) {
clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
}
clusters.push(shadeColor(theme, 0.1))
return clusters
}
}
}
</script>
<style>
.theme-message,
.theme-picker-dropdown {
z-index: 99999 !important;
}
.theme-picker .el-color-picker__trigger {
height: 26px !important;
width: 26px !important;
padding: 2px;
}
.theme-picker-dropdown .el-color-dropdown__link-btn {
display: none;
}
</style>
主题持久化
当项目中成功接入上述组件后,我们点击了一下切换会惊喜的发现,组件颜色成功切换了!
但是不要开心的太早,因为无法持久化,当你刷新页面主题就会丢失,无法保持状态,接下来让我们来实现主题持久化~
- 我们将主题持久化的函数全部抽离成为了混入文件
- 在项目初始化也就是APP.vue的created中引入,进行初始化处理
- 刷新页面测试,主题已经完成持久化
实现代码:
//加载皮肤用
const version = require('element-ui/package.json').version // element-ui version from node_modules
const ORIGINAL_THEME = '#409EFF' // default color
export default {
methods: {
updateStyle(style, oldCluster, newCluster) {
let newStyle = style
oldCluster.forEach((color, index) => {
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
})
return newStyle
},
getCSSString(url, variable) {
return new Promise(resolve => {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
resolve()
}
}
xhr.open('GET', url)
xhr.send()
})
},
getThemeCluster(theme) {
const tintColor = (color, tint) => {
let red = parseInt(color.slice(0, 2), 16)
let green = parseInt(color.slice(2, 4), 16)
let blue = parseInt(color.slice(4, 6), 16)
if (tint === 0) {
// when primary color is in its rgb space
return [red, green, blue].join(',')
} else {
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)
let green = parseInt(color.slice(2, 4), 16)
let 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++) {
clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
}
clusters.push(shadeColor(theme, 0.1))
return clusters
},
//换皮肤 防止F5后皮肤丢失
async handleUserTheme() {
let val = this.$store.getters['base/getTheme']
const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
if (typeof val !== 'string') return
const themeCluster = this.getThemeCluster(val.replace('#', ''))
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
console.log(themeCluster, originalCluster)
const getHandler = (variable, id) => {
return () => {
const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
let styleTag = document.getElementById(id)
if (!styleTag) {
styleTag = document.createElement('style')
styleTag.setAttribute('id', id)
document.head.appendChild(styleTag)
}
styleTag.innerText = newStyle
}
}
if (!this.chalk) {
const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
await this.getCSSString(url, 'chalk')
}
const chalkHandler = getHandler('chalk', 'chalk-style')
chalkHandler()
const styles = [].slice.call(document.querySelectorAll('style')).filter(style => {
const text = style.innerText
return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
})
styles.forEach(style => {
const { innerText } = style
if (typeof innerText !== 'string') return
style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
})
}
}
}
项目框架主题
上述我们已经完成了组件库主题的自定义切换,接下来我们就来实现项目框架主题的自定义切换。 在做此方案之前我们用的是切换类的模式,虽然也能完成主题切换,但每次新增一套主题时,就要去修改代码,相当麻烦。
历史方案介绍
提前引入所有主题样式,做类名切换,这种方案是在需要切换主题的时候将指定的根元素类名更换,进行样式覆盖,同时各个类名下的样式也就同步切换了。
实现方案:
/* 白天样式主题 */
body.day .box {
color: #000;
background: #fff;
}
/* 黑夜样式主题 */
body.dark .box {
color: #fff;
background: #000;
}
.box {
width: 100px;
height: 100px;
border: 1px solid #000;
}
<div class="box">
<p>one piece</p>
</div>
<p>
选择样式:
<button onclick="change('day')">白天主题切换</button>
<button onclick="change('dark')">夜晚主题切换</button>
</p>
function change(theme) {
document.body.className = theme;
}
废弃原因:
- 首屏加载时会牺牲一些时间加载样式资源
- 如果主题样式表内定义不当,也会有优先级问题
- 各个主题样式是写死的,后续针对某一主题样式表修改或者新增主题也很麻烦
现有方案!
针对客户不断提出的各种换肤,频繁做着无效劳动去增加主题,让我倍感烦躁。所以推出了以下方案。
现有方案的核心有两点:
- scss中的
var
变量 - js语法
style.setProperty
利用这两点我们完成了项目中的主题0代码修改切换,而且可以同步组件库。
定义公共变量
定义公用scss变量,这里定义了3个变量,有同学可能会疑惑,这色值不是一样的,不要急,下来会一步一步讲
$brand-color:var(--brand-color, #166bff);
$brand-color-light:var(--brand-color-light, #166bff);
$brand-color-dark:var(--brand-color-dark, #166bff);
定义了scss变量后,我们为了方便使用,需要直接在webpack中进行全局变量配置
// 配置 scss 全局变量,不用在使用的每一处再引入变量文件
// 打包切换主题可在此处完成
webpack.module.rule('scss').oneOfs.store.forEach(item => {
item
.use('sass-resources-loader')
.loader('sass-resources-loader')
.options({
resources: [resolve('./src/style/templateTheme.scss')]
})
})
在项目中使用
<style lang="scss" scoped>
.home{
background-color: $brand-color;
}
</style>-
同步主题选择器颜色
上述我们介绍了主题选择器,以及出题存储,这里我们就要讲获取到的主题进行赋值,同步到项目框架中
data() {
return {
currentTheme: this.$store.getters['base/getTheme']
}
},
mounted() {
this.setTemplateColor()
},
methods: {
setTemplateColor() {
// 设置主色
function setTheme(name, color) {
document
.getElementsByTagName('body')[0]
.style.setProperty(name, color)
}
// 根据主色计算衍生色
const colorObj = {
'--brand-color': this.currentTheme,
'--brand-color-dark': this.lightDarkenColor(this.currentTheme, -10),
'--brand-color-light': this.colorRgba(this.currentTheme, 0.6)
}
Object.entries(colorObj).forEach(e => {
// 逐个设置 css 变量到 body 上
setTheme(e[0],e[1])
})
},
}
大家可以看到,上述有个根据主色计算衍生色的算法,这里来解答上述疑问。
衍生色计算
在项目中我们往往不可能只使用一种主色,一般会伴随集中衍生色调的使用,例如hover颜色,选中颜色等。 所以我们就需要根据主色,去计算出其他颜色。 这里提供几种色阶计算的方案:
- 获取rgb颜色
// 获取 rgb 颜色值
getRgbNum(sColor) {
if (sColor.length === 4) {
let sColorNew = '#'
for (let i = 1; i < 4; i += 1) {
// 补全颜色值 例如:#eee,#fff等
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
}
sColor = sColorNew
}
// 处理六位颜色值
let sColorChange = []
for (let i = 1; i < 7; i += 2) {
// 核心代码,通过parseInt将十六进制转为十进制,parseInt只有一个参数时是默认转为十进制的,第二个参数则是指定转为对应进制
sColorChange.push(parseInt('0x' + sColor.slice(i, i + 2)))
}
return sColorChange
},
- 透明度色阶
// str: 十六进制颜色值,n:透明度
colorRgba(str, n) {
// 十六进制颜色值的正则表达式
let reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
let sColor = str.toLowerCase()
n = n || 1
// 十六进制颜色转换为RGB格式
if (sColor && reg.test(sColor)) {
let sColorChange = this.getRgbNum(sColor)
return 'rgba(' + sColorChange.join(',') + ',' + n + ')'
} else {
return sColor
}
},
- 加深或减弱颜色值
lightDarkenColor(color, num) {
let colorArr = this.getRgbNum(color)
let sColorChange = []
for (let i = 0; i < colorArr.length; i++) {
let val = colorArr[i] + num
if (val < 0) {
val = 0
}
if (val > 255) {
val = 255
}
sColorChange.push(val)
}
return 'rgba(' + sColorChange.join(',') + ',1)'
}
- 指定色阶计算
此种方案需要指定初始颜色
和结束颜色
以及色阶数量
,用来生成想要的色阶
// 色阶计算
stepColor(aColor, bColor, num) {
function gradientColor(startColor, endColor, step) {
let startRGB = this.colorRgb(startColor) //转换为rgb数组模式
let startR = startRGB[0]
let startG = startRGB[1]
let startB = startRGB[2]
let endRGB = this.colorRgb(endColor)
let endR = endRGB[0]
let endG = endRGB[1]
let endB = endRGB[2]
let sR = (endR - startR) / step //总差值
let sG = (endG - startG) / step
let sB = (endB - startB) / step
var colorArr = []
for (var i = 0; i < step; i++) {
//计算每一步的hex值
var hex = this.colorHex(
'rgb(' +
parseInt(sR * i + startR) +
',' +
parseInt(sG * i + startG) +
',' +
parseInt(sB * i + startB) +
')'
)
colorArr.push(hex)
}
return colorArr
}
// 将hex表示方式转换为rgb表示方式(这里返回rgb数组模式)
gradientColor.prototype.colorRgb = function colorRgb(sColor) {
var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
var sColor = sColor.toLowerCase()
if (sColor && reg.test(sColor)) {
if (sColor.length === 4) {
var sColorNew = '#'
for (var i = 1; i < 4; i += 1) {
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
}
sColor = sColorNew
}
//处理六位的颜色值
var sColorChange = []
for (var i = 1; i < 7; i += 2) {
sColorChange.push(parseInt('0x' + sColor.slice(i, i + 2)))
}
return sColorChange
} else {
return sColor
}
}
// 将rgb表示方式转换为hex表示方式
gradientColor.prototype.colorHex = function (rgb) {
var _this = rgb
var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
if (/^(rgb|RGB)/.test(_this)) {
var aColor = _this.replace(/(?:(|)|rgb|RGB)*/g, '').split(',')
var strHex = '#'
for (var i = 0; i < aColor.length; i++) {
var hex = Number(aColor[i]).toString(16)
hex = hex < 10 ? 0 + '' + hex : hex // 保证每个rgb的值为2位
if (hex === '0') {
hex += hex
}
strHex += hex
}
if (strHex.length !== 7) {
strHex = _this
}
return strHex
} else if (reg.test(_this)) {
var aNum = _this.replace(/#/, '').split('')
if (aNum.length === 6) {
return _this
} else if (aNum.length === 3) {
var numHex = '#'
for (var i = 0; i < aNum.length; i += 1) {
numHex += aNum[i] + aNum[i]
}
return numHex
}
} else {
return _this
}
}
let gradient = new gradientColor(aColor, bColor, num);
return gradient
}
方案总结
核心源码
//加载皮肤用
const version = require('element-ui/package.json').version // element-ui version from node_modules
const ORIGINAL_THEME = '#409EFF' // default color
export default {
data() {
return {
currentTheme: this.$store.getters['base/getTheme']
}
},
mounted() {
this.setTemplateColor()
},
methods: {
setTemplateColor() {
// 设置主色
function setTheme(name, color) {
document.getElementsByTagName('body')[0].style.setProperty(name, color)
}
// 根据主色计算衍生色
const colorObj = {
'--brand-color': this.currentTheme,
'--brand-color-dark-1': this.lightDarkenColor(this.currentTheme, -10),
'--brand-color-dark-2': this.lightDarkenColor(this.currentTheme, -20),
'--brand-color-dark-3': this.lightDarkenColor(this.currentTheme, -30),
'--brand-color-dark-4': this.lightDarkenColor(this.currentTheme, -40),
'--brand-color-dark-5': this.lightDarkenColor(this.currentTheme, -50),
'--brand-color-dark-6': this.lightDarkenColor(this.currentTheme, -60),
'--brand-color-dark-7': this.lightDarkenColor(this.currentTheme, -70),
'--brand-color-dark-8': this.lightDarkenColor(this.currentTheme, -80),
'--brand-color-dark-9': this.lightDarkenColor(this.currentTheme, -90),
'--brand-color-opacity-1': this.colorRgba(this.currentTheme, 0.9),
'--brand-color-opacity-2': this.colorRgba(this.currentTheme, 0.8),
'--brand-color-opacity-3': this.colorRgba(this.currentTheme, 0.7),
'--brand-color-opacity-4': this.colorRgba(this.currentTheme, 0.6),
'--brand-color-opacity-5': this.colorRgba(this.currentTheme, 0.5),
'--brand-color-opacity-6': this.colorRgba(this.currentTheme, 0.4),
'--brand-color-opacity-7': this.colorRgba(this.currentTheme, 0.3),
'--brand-color-opacity-8': this.colorRgba(this.currentTheme, 0.2),
'--brand-color-opacity-9': this.colorRgba(this.currentTheme, 0.1),
'--brand-color-light-1': this.lightDarkenColor(this.currentTheme, 10),
'--brand-color-light-2': this.lightDarkenColor(this.currentTheme, 20),
'--brand-color-light-3': this.lightDarkenColor(this.currentTheme, 30),
'--brand-color-light-4': this.lightDarkenColor(this.currentTheme, 40),
'--brand-color-light-5': this.lightDarkenColor(this.currentTheme, 50),
'--brand-color-light-6': this.lightDarkenColor(this.currentTheme, 60),
'--brand-color-light-7': this.lightDarkenColor(this.currentTheme, 70),
'--brand-color-light-8': this.lightDarkenColor(this.currentTheme, 80),
'--brand-color-light-9': this.lightDarkenColor(this.currentTheme, 90),
'--brand-color-step-1': this.stepColor(this.currentTheme,'#ffffff', 10)[0],
'--brand-color-step-2': this.stepColor(this.currentTheme,'#ffffff', 10)[1],
'--brand-color-step-3': this.stepColor(this.currentTheme,'#ffffff', 10)[2],
'--brand-color-step-4': this.stepColor(this.currentTheme,'#ffffff', 10)[3],
'--brand-color-step-5': this.stepColor(this.currentTheme,'#ffffff', 10)[4],
'--brand-color-step-6': this.stepColor(this.currentTheme,'#ffffff', 10)[5],
'--brand-color-step-7': this.stepColor(this.currentTheme,'#ffffff', 10)[6],
'--brand-color-step-8': this.stepColor(this.currentTheme,'#ffffff', 10)[7],
'--brand-color-step-9': this.stepColor(this.currentTheme,'#ffffff', 10)[8],
'--brand-color-step-dark-1': this.stepColor(this.currentTheme,'#000000', 10)[0],
'--brand-color-step-dark-2': this.stepColor(this.currentTheme,'#000000', 10)[1],
'--brand-color-step-dark-3': this.stepColor(this.currentTheme,'#000000', 10)[2],
'--brand-color-step-dark-4': this.stepColor(this.currentTheme,'#000000', 10)[3],
'--brand-color-step-dark-5': this.stepColor(this.currentTheme,'#000000', 10)[4],
'--brand-color-step-dark-6': this.stepColor(this.currentTheme,'#000000', 10)[5],
'--brand-color-step-dark-7': this.stepColor(this.currentTheme,'#000000', 10)[6],
'--brand-color-step-dark-8': this.stepColor(this.currentTheme,'#000000', 10)[7],
'--brand-color-step-dark-9': this.stepColor(this.currentTheme,'#000000', 10)[8]
}
Object.entries(colorObj).forEach(e => {
// 逐个设置 css 变量到 body 上
setTheme(e[0], e[1])
})
},
updateStyle(style, oldCluster, newCluster) {
let newStyle = style
oldCluster.forEach((color, index) => {
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
})
return newStyle
},
getCSSString(url, variable) {
return new Promise(resolve => {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
resolve()
}
}
xhr.open('GET', url)
xhr.send()
})
},
getThemeCluster(theme) {
const tintColor = (color, tint) => {
let red = parseInt(color.slice(0, 2), 16)
let green = parseInt(color.slice(2, 4), 16)
let blue = parseInt(color.slice(4, 6), 16)
if (tint === 0) {
// when primary color is in its rgb space
return [red, green, blue].join(',')
} else {
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)
let green = parseInt(color.slice(2, 4), 16)
let 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++) {
clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
}
clusters.push(shadeColor(theme, 0.1))
return clusters
},
//换皮肤 防止F5后皮肤丢失
async handleUserTheme() {
let val = this.$store.getters['base/getTheme']
const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
if (typeof val !== 'string') return
const themeCluster = this.getThemeCluster(val.replace('#', ''))
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
console.log(themeCluster, originalCluster)
const getHandler = (variable, id) => {
return () => {
const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
let styleTag = document.getElementById(id)
if (!styleTag) {
styleTag = document.createElement('style')
styleTag.setAttribute('id', id)
document.head.appendChild(styleTag)
}
styleTag.innerText = newStyle
}
}
if (!this.chalk) {
const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
await this.getCSSString(url, 'chalk')
}
const chalkHandler = getHandler('chalk', 'chalk-style')
chalkHandler()
const styles = [].slice.call(document.querySelectorAll('style')).filter(style => {
const text = style.innerText
return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
})
styles.forEach(style => {
const { innerText } = style
if (typeof innerText !== 'string') return
style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
})
},
// str: 十六进制颜色值,n:透明度
colorRgba(str, n) {
// 十六进制颜色值的正则表达式
let reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
let sColor = str.toLowerCase()
n = n || 1
// 十六进制颜色转换为RGB格式
if (sColor && reg.test(sColor)) {
let sColorChange = this.getRgbNum(sColor)
return 'rgba(' + sColorChange.join(',') + ',' + n + ')'
} else {
return sColor
}
},
// 获取 rgb 颜色值
getRgbNum(sColor) {
if (sColor.length === 4) {
let sColorNew = '#'
for (let i = 1; i < 4; i += 1) {
// 补全颜色值 例如:#eee,#fff等
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
}
sColor = sColorNew
}
// 处理六位颜色值
let sColorChange = []
for (let i = 1; i < 7; i += 2) {
// 核心代码,通过parseInt将十六进制转为十进制,parseInt只有一个参数时是默认转为十进制的,第二个参数则是指定转为对应进制
sColorChange.push(parseInt('0x' + sColor.slice(i, i + 2)))
}
return sColorChange
},
// 加深或减弱颜色值
lightDarkenColor(color, num) {
let colorArr = this.getRgbNum(color)
let sColorChange = []
for (let i = 0; i < colorArr.length; i++) {
let val = colorArr[i] + num
if (val < 0) {
val = 0
}
if (val > 255) {
val = 255
}
sColorChange.push(val)
}
return 'rgba(' + sColorChange.join(',') + ',1)'
},
// 色阶计算
stepColor(aColor, bColor, num) {
function gradientColor(startColor, endColor, step) {
let startRGB = this.colorRgb(startColor) //转换为rgb数组模式
let startR = startRGB[0]
let startG = startRGB[1]
let startB = startRGB[2]
let endRGB = this.colorRgb(endColor)
let endR = endRGB[0]
let endG = endRGB[1]
let endB = endRGB[2]
let sR = (endR - startR) / step //总差值
let sG = (endG - startG) / step
let sB = (endB - startB) / step
var colorArr = []
for (var i = 0; i < step; i++) {
//计算每一步的hex值
var hex = this.colorHex(
'rgb(' +
parseInt(sR * i + startR) +
',' +
parseInt(sG * i + startG) +
',' +
parseInt(sB * i + startB) +
')'
)
colorArr.push(hex)
}
return colorArr
}
// 将hex表示方式转换为rgb表示方式(这里返回rgb数组模式)
gradientColor.prototype.colorRgb = function colorRgb(sColor) {
var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
var sColor = sColor.toLowerCase()
if (sColor && reg.test(sColor)) {
if (sColor.length === 4) {
var sColorNew = '#'
for (var i = 1; i < 4; i += 1) {
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
}
sColor = sColorNew
}
//处理六位的颜色值
var sColorChange = []
for (var i = 1; i < 7; i += 2) {
sColorChange.push(parseInt('0x' + sColor.slice(i, i + 2)))
}
return sColorChange
} else {
return sColor
}
}
// 将rgb表示方式转换为hex表示方式
gradientColor.prototype.colorHex = function (rgb) {
var _this = rgb
var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
if (/^(rgb|RGB)/.test(_this)) {
var aColor = _this.replace(/(?:(|)|rgb|RGB)*/g, '').split(',')
var strHex = '#'
for (var i = 0; i < aColor.length; i++) {
var hex = Number(aColor[i]).toString(16)
hex = hex < 10 ? 0 + '' + hex : hex // 保证每个rgb的值为2位
if (hex === '0') {
hex += hex
}
strHex += hex
}
if (strHex.length !== 7) {
strHex = _this
}
return strHex
} else if (reg.test(_this)) {
var aNum = _this.replace(/#/, '').split('')
if (aNum.length === 6) {
return _this
} else if (aNum.length === 3) {
var numHex = '#'
for (var i = 0; i < aNum.length; i += 1) {
numHex += aNum[i] + aNum[i]
}
return numHex
}
} else {
return _this
}
}
let gradient = new gradientColor(aColor, bColor, num);
return gradient
}
}
}
这里就已经完整的介绍了,整个主题切换的逻辑,无论多少种换肤主题,我们都无需修改代码,省时省力。
注意
- 初始化混入需在项目执行前引入,否则可能会出现换肤闪烁~
- 注意公用scss文件的维护,标准化处理
最后希望本方案能够帮助到大家!对你有用的话,就点个赞吧~