vue项目主题切换(换肤)从0-1的实现


前期准备

对一个老项目增加切换主题的需求,从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

猜你喜欢

转载自blog.csdn.net/weixin_43695002/article/details/126677434
今日推荐