vue 动态更换主题色

需求是选中自定义颜色,更换相应的主题色。

一、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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容