文章目录
前期准备
对一个老项目增加切换主题的需求,从0到1的实现过程以及总结
切换的主题涉及到两种模式:
1、默认主题
2、黑夜主题
一、切换主题需求分析
以上是我画的一个简单示意图,下面是总结
1.通过dom操作切换link标签进行切换引入element ui 的主题文件(主题文件放在public静态资源中)
2.每个主题色值存储为js的key、value的形式,并通过js挂在到全局为var变量
3.通过cssVars插件对使用的var变量进行转换(解决ie不支持var的兼容性问题)
4.由于element ui可配置主题的内容太少,通过css进行组件样式的覆盖从而进行可定义
5.为每个16进制色值增加rgba的变量
6.向window下暴漏公共接口(色值的获取、修改等)为chrome的色值修改插件做铺垫
7.色值存储在全局的sotre中,用于js代码对全局色值的引用
以上是换肤需要做的事,下面我会详细介绍
二、代码详解
1.目录介绍
2.切换主题方法代码
代码如下(示例):
//index.js
import {
color as defaultThemeColor } from './default-config.js'//默认主题
import {
color as darkThemeColor } from './dark-config.js'//暗黑主题
import store from '@/store/store'
import cssVars from 'css-vars-ponyfill'
import {
fromHex } from '@/utils/index'//css颜色16进制转rgba方法
export const color = {
default: colorToRgba(defaultThemeColor),
dark: colorToRgba(darkThemeColor)
}
// 把16进制颜色对象增加对应的rgb颜色值
export function colorToRgba (color) {
const param = {
...color }
Object.keys(param).forEach(key => {
if (param[key].includes('#')) {
// 16进制颜色
const {
r, g, b } = fromHex(param[key])
param[`${
key}-rgb`] = `${
r},${
g},${
b}`
}
})
return param
}
export function changeTheme (themeValue) {
const body = document.body
body.className = 'theme-' + themeValue
// 当前使用的色表,以js形式存储
const colorTheme = Object.keys(store.state.home.themeColor).length > 0 && themeValue === localStorage.getItem('themeValue') ? store.state.home.themeColor : color[themeValue]
// 把使用的主题色表存在vuex中,以便js使用色值的使用使用
store.dispatch('setThemeColor', colorTheme)
// 保存当前主题类型
localStorage.setItem('themeValue', 'default')
store.dispatch('setActiveTheme', themeValue)
// 获取element ui 组件的主题路径(存在静态文件里)
const staticPath = '/static'
const itemPath = staticPath + '/theme/' + themeValue + '/index.css'
loadCss(itemPath)
// 存储当前主题当本地
localStorage.setItem('themeValue', themeValue)
// 获取整个dom
const root = document.querySelector(':root') || document.documentElement
// 遍历主题样式配色
if (root && colorTheme) {
for (const key in colorTheme) {
// 寻找对应的主题色
if (Object.hasOwnProperty.call(colorTheme, key)) {
// 把主题的样式设置根的style上(相当于设置全局的变量)
root.style.setProperty(key, colorTheme[key])
}
}
}
// 由于ie不支持var获取全局变量,colorTheme主题键值对使用下面的插件进行处理后就会转为转生的颜色
cssVars({
watch: true,
// variables 自定义属性名/值对的集合
variables: colorTheme,
// 当添加,删除或修改其<link>或<style>元素的禁用或href属性时,ponyfill将自行调用
// false 默认将css变量编译为浏览器识别的css样式 true 当浏览器不支持css变量的时候将css变量编译为识别的css
onlyLegacy: false
})
// 为整个html设置引入element ui的主题样式
function loadCss (path) {
const head = document.getElementsByTagName('head')[0]
const themeLink = document.getElementById('theme')
if (themeLink) {
// 若存在旧的link,则创建新的link把旧的link替换掉(link 引入element ui主题)
// 创建新的link dom
const link = document.createElement('link')
link.href = path
link.rel = 'stylesheet'
link.type = 'text/css'
// 把新dom插入到当前元素的下边
insertAfter(link, themeLink)
// 当通过link引入的css加载完成以后,删除旧的css并把新的link赋上id
onloadCss(link, () => {
themeLink.parentNode.removeChild(themeLink)
link.id = 'theme'
})
} else {
const link = document.createElement('link')
link.href = path
link.rel = 'stylesheet'
link.type = 'text/css'
link.id = 'theme'
// 把link插入到head第一个子元素之前(head.childNodes[0])
head.insertBefore(link, head.childNodes[0])
}
}
}
/**
* @name 监听css文件的加载进度
* @param node {isDom} 监听的link dom
* @param fn {function} 加载结束回调函数
* */
// 监听新的dom(node)是否加载完成
function onloadCss (node, fn) {
if (node.attachEvent) {
// IE
node.attachEvent('onload', function () {
fn(null, node)
})
} else {
// other browser
setTimeout(function () {
poll(node, fn)
}, 0)
}
function poll (node, callback) {
let isLoaded = false
if (/webkit/i.test(navigator.userAgent)) {
// webkit
if (node.sheet) {
isLoaded = true
}
} else if (node.sheet) {
// for Firefox
try {
if (node.sheet.cssRules) {
isLoaded = true
}
} catch (ex) {
// NS_ERROR_DOM_SECURITY_ERR
if (ex.code === 1000) {
isLoaded = true
}
}
}
if (isLoaded) {
setTimeout(function () {
callback(null, node)
}, 1)
} else {
// 若没有加载完,则在10毫秒以后继续判断
setTimeout(function () {
poll(node, callback)
}, 10)
}
}
}
function insertAfter (newNode, curNode) {
// newNode插入到curNode.nextElementSibling节点之前
// 也就是把新节点newNode插入到当前节点curNode的下一个节点之前(当前节点之后)
curNode.parentNode.insertBefore(newNode, curNode.nextElementSibling)
}
以上代码重点就是对外暴漏的 changeTheme () 方法进行使用
3.主题的使用
在main.js以及主题切换按钮进行调用,传入default或dark等不同的主题标识
//main.js
// 加载用户主题
const theme = localStorage.getItem('themeValue')
if (theme) {
changeTheme(theme)
} else {
changeTheme('default')
}
//index.vue(按钮切换主题方法)
handleSetTheme (theme) {
if(theme===this.activeTheme){
return}
this.$store.dispatch('setActiveTheme', theme)
changeTheme(theme)
}
4.原项目中的更改
1.全局色值都改为变量形式( js的色值从store中取)
如echarts等配置设置色值写的是js代码,此时需要从store进行引入:
label: {
show: true,
color: this.$store.state.home.themeColor['--text-color-primary'],
},
var引用或者scss变量形式引用:
//var形式
.el-button.el-button--default:not(.is-disabled){
color: var(--button-default-font);
}
//scss变量形式
.el-header {
background-color: $card-primary;
box-shadow: -1px 1px 26px -13px rgba($color-shadow-rgb,.5);
}
2.设置scss的16进制变量(踩坑)
基于一个色值变量设置不同透明度:
1.方式1(错误)
body{
--color:#ffffff
}
$color:var(#ffffff)
rgba(var(--color),0.5)
rgba($color,0.5)
注意:ragb里面不可有var(),因此上面两个rbga都有问题,应改为下面方式:
2.方式2
body{
--color-rgb:255,255,255
}
rgba($color-rgb,0.5)
需要在scss文件中定义这种数值型的值,然后进行引用才行,因此在index.js文件中会有colorToRgba方法为所有16进制color变量生成对应的rgb格式变量color-rgb
3.增加配置文件(粗略浏览即可)
1.主题配置文件
//dark-config.js(黑夜主题文件)
export const color = {
// 主题色
'--color-primary': '#AD3393',
'--color-primary-hover': '#BD5CA9',
/* 主题按钮 */
'--button-primary-font': '#ffffff', // 按钮文字颜色
'--button-primary-background': '#AD3393', // 按钮背景色
'--button-primary-border': '#AD3393', // 按钮边框
'--button-primary-hover-font': '#ffffff', // 移入按钮文字颜色
'--button-primary-hover-background': '#BD5CA9', // 移入按钮背景色
'--button-primary-hover-border': '#BD5CA9', // 移入按钮边框
// 表格
// 表头背景底色
'--table-header-background': '#4A4A59',
'--table-row-background': '#222235', // 表格行背景底色
'--table-top-background': '#3F3F4F', // 置顶时的底色
'--table-head-font-color': '#BCBCC2', // 表头文字
'--table-row-font-color': '#B5B5BB', // 表格行文字颜色
'--table-td-border-bottom-color': '#383849' // 表格单元格下边框
}
4.element ui配置文件放在public静态文件中
由于在切换element ui主题的时候是通过切换html页面根位置的link标签进行的,所以要放在静态文件的位置,通过绝对路径进行引入
const staticPath = '/static'
const itemPath = staticPath + '/theme/' + themeValue + '/index.css'
//改变link引入切换主题
loadCss(itemPath)
5.chrome插件相关部分(在此仅介绍思路)
chomre插件学习(两个链接内容一样)
github
国内博客
其他同事写了一个chomre插件,可以通过chomre插件修改色值进行预览,之后可以进行配置文件的下载,更加方便其他开发人员以及ui进行设计。
插件思路:
通过项目中暴露给window的获取、修改色值等方法进行色值的展示以及修改,点击预览后调用changTheme方法进行主题更改,这样实现了利用插件实时更改主题,并可以把配置好的主题文件下载下来让开发进行替换。
1.为window添加方法
/*
以下为暴漏给外部的接口可供修改颜色
*/
// 当前存储的所有色值
window.allColor = function () {
return store.state.home.themeColor
}
// 存储当前色值对应的各种配置(包括显示中文等)
window.chromeConfig = function () {
return chromeConfig
}
// 更改主题色值的方法
window.setThemeColor = function (colorTheme) {
const theme = localStorage.getItem('themeValue')
const newColor = Object.keys(colorTheme).length
? colorTheme
: theme ? initColor[theme] : initColor.default
store.dispatch('setThemeColor', colorToRgba(newColor))
}
window.changeTheme = changeTheme
2.上述代码中的chromeConfig 配置(删减省略后)
/*
此文件为暴漏给插件的文件
describe:每个色值的中文描述
default:默认主题的色值
dark:夜晚主题的色值
type:
public:公共色值
input...:表格色值
(除public为公共色值外,其他为组件类型时(如input)用以判断色值的类别)
*/
import {
color as defaultThemeColor } from './default-config.js'
import {
color as darkThemeColor } from './dark-config.js'
const publicColor = {
//公共样式配置
'--color-danger': {
describe: '高危',
type: 'public'
},
}
const componentColor = {
//组件样式单独配置
'--input-border-color': {
describe: '输入框-边框',
type: 'input'
},
'--input-content-color': {
describe: '输入框-输入内容',
type: 'input'
},
'--input-placeholder-color': {
describe: '输入框-提示内容',
type: 'input'
},
}
const colorConfig = {
//总配置
...publicColor,
...componentColor,
'--text-color-secondary': {
describe: '描述等文字'
},
}
Object.keys(defaultThemeColor).forEach(key => {
if (!colorConfig[key]) {
console.error('色值配置文件项应与描述配置文件变量相统一')
return {
}
}
colorConfig[key].default = defaultThemeColor[key]
colorConfig[key].dark = darkThemeColor[key]
})
3.插件效果
总结
提示:以上是我对老项目增加切换主题功能的心得总结,希望可以多提建议。
有不懂的可以及时留言或者联系微信a13716670638