需求是选中自定义颜色,更换相应的主题色。
一、CSS变量+动态setProperty
这种方式适合自定义颜色,然后通过改变颜色变量去动态更改主题色,一般项目前期定义好。
实现:
1、创建一个scss文件,预设全局主题色。
:root {
--main-bg-color: #409EFF;
}
2、定义一个工具类方法,用于修改指定的CSS变量值,调用的是CSSStyleDeclaration.setProperty[4]
// 动态修改自动定义颜色
export const setCssVar = (prop, val, dom = document.documentElement) => {
dom.style.setProperty(prop, val)
}
3、全局使用定义的颜色变量
div {
background-color: var(--main-bg-color);
}
4、方法调用
import { setCssVar } from '@/utils/changeTheme'
setCssVar('--main-bg-color', value) // value 是颜色值,比如:#6959CD
参考文章:https://zhuanlan.zhihu.com/p/572848338
二、基于Elementui框架,动态修改框架主题色
简单说明一下它的原理: element-ui 2.0 版本之后所有的样式都是基于 SCSS 编写的,所有的颜色都是基于几个基础颜色变量来设置的,所以就不难实现动态换肤了,只要找到那几个颜色变量修改它就可以了。 首先我们需要拿到通过 package.json 拿到 element-ui 的版本号,根据该版本号去请求相应的样式。拿到样式之后将样色,通过正则匹配和替换,将颜色变量替换成你需要的,之后动态添加 style 标签来覆盖原有的 css 样式。
基于网上的内容,自己总结了一下:
1、创建一个element-variables.scss文件。并在main.js中引入
/* 改变主题色变量 */
$--color-primary: #409EFF;
$--color-success: #7AC800;
$--color-danger: hsl(138, 82%, 59%);
$--color-info: #999999;
/* 改变 icon 字体路径变量,必需 */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
@import "~element-ui/packages/theme-chalk/src/index";
/* 如遇到导出的变量值为 undefined 则本文件名需要改成 element-variables.module.scss */
:export {
theme: $--color-primary
}
main.js
import ElementUI from 'element-ui';
// import 'element-ui/lib/theme-chalk/index.css';
import '@/styles/element-variables.scss'
2、创建一个js文件,核心代码就是替换颜色变量,简单封装。
可以直接copy,更换一下loading加载框就好。
import Vue from 'vue';
const vue = new Vue()
const version = require('element-ui/package.json').version // element-ui version from node_modules
const ORIGINAL_THEME = '#409EFF' // default color
let chalk = null
let theme = null
let chalkObj = {}
export async function changeTheme(val) {
const oldVal = chalk ? theme : ORIGINAL_THEME
if (typeof val !== 'string') return
const themeCluster = getThemeCluster(val.replace('#', ''))
const originalCluster = 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'
// })
vue.$loads.show()
const getHandler = (variable, id) => {
return () => {
const originalCluster = getThemeCluster(ORIGINAL_THEME.replace('#', ''))
const newStyle = updateStyle(chalkObj[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 (!chalk) {
const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
await 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 = updateStyle(innerText, originalCluster, themeCluster)
})
// this.$emit('change', val)
// $message.close()
vue.$loads.hidden()
}
function updateStyle(style, oldCluster, newCluster) {
let newStyle = style
oldCluster.forEach((color, index) => {
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
})
return newStyle
}
function getCSSString(url, variable) {
return new Promise(resolve => {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
chalkObj[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
resolve()
}
}
xhr.open('GET', url)
xhr.send()
})
}
function 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
}
3、ElementUI提供的自定义颜色组件
<el-color-picker
v-model="themeColor"
:predefine="predefineTheme"
class="theme-picker"
popper-class="theme-picker-dropdown"
/>
export default {
data() {
return {
// 存放在vuex 中,是为了缓存
themeColor: this.$store.state.settings.theme ? this.$store.state.settings.theme : null,
}
}
}
4、调用方法
import { changeTheme} from '@/utils/changeTheme'
changeTheme(value)
5、为了防止颜色丢失,在app.vue中我也调用了次方法
<script>
import { getThemeColor } from '@/utils/cach'
import { changeTheme } from '@/utils/changeTheme'
export default {
mounted() {
// getThemeColor() 获取缓存的颜色方法
let themeColor = getThemeColor()
if (themeColor) {
changeTheme(value)
}
},
methods: {
...mapActions(['changeSetting'])
}
}
</script>
三、为了修改动态主题色,我结合了两种方法,做了简单封装
废话不多说,直接上代码
1、创建封装的js文件,/utils/changeTheme.js
import Vue from 'vue';
const vue = new Vue()
const version = require('element-ui/package.json').version // element-ui version from node_modules
const ORIGINAL_THEME = '#409EFF' // default color
let chalk = null
let theme = null
let chalkObj = {}
// 动态修改elementUI主题色
export async function changeTheme(val) {
const oldVal = chalk ? theme : ORIGINAL_THEME
if (typeof val !== 'string') return
const themeCluster = getThemeCluster(val.replace('#', ''))
const originalCluster = 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'
// })
vue.$loads.show()
const getHandler = (variable, id) => {
return () => {
const originalCluster = getThemeCluster(ORIGINAL_THEME.replace('#', ''))
const newStyle = updateStyle(chalkObj[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 (!chalk) {
const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
await 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 = updateStyle(innerText, originalCluster, themeCluster)
})
// this.$emit('change', val)
// $message.close()
vue.$loads.hidden()
}
function updateStyle(style, oldCluster, newCluster) {
let newStyle = style
oldCluster.forEach((color, index) => {
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
})
return newStyle
}
function getCSSString(url, variable) {
return new Promise(resolve => {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
chalkObj[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
resolve()
}
}
xhr.open('GET', url)
xhr.send()
})
}
function 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
}
// 动态修改自动定义颜色
export const setCssVar = (prop, val, dom = document.documentElement) => {
dom.style.setProperty(prop, val)
}
2、创建scss文件,/styles/element-variables.scss,并在main.js中引入
/* 改变主题色变量 */
$--color-primary: #409EFF;
$--color-success: #7AC800;
$--color-danger: hsl(138, 82%, 59%);
$--color-info: #999999;
/* 改变 icon 字体路径变量,必需 */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
@import "~element-ui/packages/theme-chalk/src/index";
/* 如遇到导出的变量值为 undefined 则本文件名需要改成 element-variables.module.scss */
:export {
theme: $--color-primary
}
main.js
import ElementUI from 'element-ui';
// import 'element-ui/lib/theme-chalk/index.css';
import '@/styles/element-variables.scss'
3、定义颜色变量,styles/index.scss
:root {
--main-bg-color: #409EFF;
}
4、创建store/moudles/settings.js文件,为了缓存主题色
import variables from '@/styles/element-variables.scss'
import { setThemeColor } from '@/utils/cach'
import { changeTheme, setCssVar } from '@/utils/changeTheme'
const settings = {
state: {
theme: variables.theme,
},
mutations: {
CHANGE_SETTING: (state, { key, value }) => {
if (state.hasOwnProperty(key)) {
state[key] = value
//localStorage 缓存起来,刷新的时候重新取用
setThemeColor(value)
// 更改背景色
changeTheme(value)
// 修改自定义的主题样式
setCssVar('--main-bg-color', value)
}
}
},
actions: {
changeSetting({ commit }, data) {
commit('CHANGE_SETTING', data)
}
}
}
export default settings
4、在.vue中使用el-color-picker组件,来自定义颜色
<el-color-picker
v-model="themeColor"
:predefine="predefineTheme"
class="theme-picker"
popper-class="theme-picker-dropdown"
@change="changeEvent"
/>
...
<script>
import { mapActions } from 'vuex'
export default {
data() {
return {
themeColor: this.$store.state.settings.theme ? this.$store.state.settings.theme : null,
predefineTheme: [
'#409EFF',
'#1890ff',
'#304156',
'#212121',
'#11a983',
'#13c2c2',
'#6959CD',
'#f5222d'],
}
},
methods: {
...mapActions(['changeSetting']),
changeEvent(val) {
this.changeSetting({ key: 'theme', value: val})
}
}
}
5、在app.vue中重新调用,防止页面刷新,颜色丢失
<script>
import { getThemeColor } from '@/utils/cach'
import { mapActions } from 'vuex';
export default {
mounted() {
let themeColor = getThemeColor()
if (themeColor) {
this.changeSetting({ key: 'theme', value: themeColor})
}
},
methods: {
...mapActions(['changeSetting'])
}
}
</script>
在获取用户信息接口的时候,应该也有返回主题色,这时候也要调用store/settings/ 中的changeSetting()。
参考文章:https://blog.csdn.net/aSmallProgrammer/article/details/128021927