Vue+ElementUI implements online dynamic skin changing

Article directory

  • I. Overview
  • 2. Realization
    • 2.1, `package.json`
    • 2.2. `element-variables.scss`
    • 2.3. `vuex` sets `store`
    • 2.4. `ThemePicker` component
      • 2.4.1、chalk.js
    • 2.5. Import `header` into the page
    • 2.6. Cache theme color
    • 2.7. Use `css` variable for theme color
  • 3. Finally

1. Overview

vue-element-admin Official implementation principle: All styles after element-ui 2.0 version are written based on SCSS, all colors They are all set based on several basic color variables, so it is not difficult to implement dynamic skin change. Just find those color variables and modify it. First, we need to get the version number of element-ui through package.json, and request the corresponding style based on the version number. After getting the style, use regular matching and replacement to replace the color variable with what you need, and then dynamically add the style tag to overwrite the original css style.

You can click [Jump] for the online experience address. The specific effect is as follows:

2. Implementation

2.1, package.json

element-ui fixed version, my project uses the 2.15.5 version, as shown below:

{
  "dependencies": {
    "element-ui": "2.15.5"
  }
}

2.2, element-variables.scss

Here is a no-brainer reference to the source code of vue-element-admin. First, create a file named element-variables.scss in the styles folder. And introduce this file in main.js.

/* src/styles/element-variables.scss */

/* Change theme color variables */
$--color-primary: #256DCB;
$--color-success: #7AC800;
$--color-danger: #EC4242;
$--color-info: #999999;

/* Change the icon font path variable, required */
$--font-path: '~element-ui/lib/theme-chalk/fonts';

@import "~element-ui/packages/theme-chalk/src/index";

/* If the exported variable value is undefined, the file name needs to be changed to element-variables.module.scss */
:export {<!-- -->
  theme: $--color-primary
}

2.3, vuex settings store

The above method has been able to achieve ElementUI theme replacement, but it is far from enough to customize the color. Considering that the theme color is used in the entire project, we save it in In Vuex, the next thing to do is to create a new settings.js under store/modules.

/* src/store/modules/settings.js */

import variables from '@/styles/element-variables.module.scss'
import * as Types from '../mutation-types'

const state = {<!-- -->
  theme: variables.theme
}

const mutations = {<!-- -->
  CHANGE_SETTING: (state, {<!-- --> key, value }) => {<!-- -->
    localStorage.setItem('theme', value) // Cache it and retrieve it when refreshing
    // eslint-disable-next-line no-prototype-builtins
    if (state.hasOwnProperty(key)) {<!-- -->
      state[key] = value
    }
  }
}

const actions = {<!-- -->
  changeSetting ({<!-- --> commit }, data) {<!-- -->
    commit('CHANGE_SETTING', data)
  }
}

export default {<!-- -->
  namespaced: true,
  state,
  mutations,
  actions
}

2.4, ThemePicker component

This step is also a brainless copy of the source code of vue-element-admin, and create a new component named ThemePicker. The code is as follows:

/* src/components/ThemePicker/index.vue */

<template>
  <el-color-picker
    v-model="theme"
    :predefine=""['#409EFF', '#04A17E', '#304156','#212121','#A99711', '#13c2c2', '#6959CD', '#f5222d']"
    class="theme-picker"
    popper-class="theme-picker-dropdown"
  />
</template>

<script>

import { chalkCss } from './chalk.js' // It is the css obtained from the getCSSString interface. Here I fix the version number and cache the css locally. Remember to deal with the \ escaping problem

// const version = require('element-ui/package.json').version // element-ui version from node_modules
const ORIGINAL_THEME = '#409EFF' // default color primary color in chalk.js, based on this color a series of colors are calculated for regular replacement

export default {
  name: 'ThemePicker',
  data () {
    return {
      chalk: '', // content of theme-chalk css
      theme: ''
    }
  },
  computed: {
    defaultTheme () {
      return this.$store.state.settings.theme
    }
  },
  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: this.$t('theme_compiling'),
        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')
        this.chalk = chalkCss.replace(/@font-face{[^}] + }/, '') // Local cache, if you need to get online, use the above method. Advantages: no delay in switching. Disadvantages : Need to manually maintain css string
      }

      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) & amp; & amp; !/Chalk Variables/.test(text)
        })
      styles.forEach(style => {
        const { innerText } = style
        if (typeof innerText !== 'string') return
        style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
      })

      this.$emit('change', val)

      $message.close()
    }
  },

  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 & amp; & amp; 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>

2.4.1, chalk.js

The chalk.js mentioned above can be accessed through the URL https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.cssDownload the file corresponding to the element-ui version. The downloaded file is css, which needs to be converted into js, as shown below:

export const chalkCss = '' // The string is the content of the downloaded index.css file, paste it all in

My chalk.js file is here, click [Download]

2.5. Import header into the page

Finally, use the ThemePicker component in the page. Here I put it in header.

//template
<theme-picker @change="themeChange" />
// js
import ThemePicker from '@/components/ThemePicker'
// methods
themeChange (val) {
  this.$store.dispatch('settings/changeSetting', {
    key: 'theme',
    value:val
  })
}

2.6, caching theme color

So far, all the dynamic skin changing functions have been implemented, but there is another problem: after refreshing the page, the theme color will be reset, so the theme color needs to be cached in localStore when switching. Go, in order to load the theme color when any page is refreshed, I chose to add a ThemePicker component to the App.vue page. For the specific implementation, see the following code:

/* src/App.vue */

<template>
  <div id="app" :style="{'--color': defaultTheme}">
    <theme-picker @change="themeChange" v-show="false" />
    <router-view />
  </div>
</template>

<script>
import ThemePicker from '@/components/ThemePicker'

export default {
  name: 'App',
  components: {ThemePicker},
  computed: {
    defaultTheme () {
      return this.$store.state.settings.theme
    }
  },
  mounted () {
    if (localStorage.getItem('theme')) {
      this.themeChange(localStorage.getItem('theme'))
    }
  },
  methods: {
    themeChange (val) {
      this.$store.dispatch('settings/changeSetting', {
        key: 'theme',
        value:val
      })
    }
  }
}
</script>

2.7. Use css variables for theme colors

If you need to use theme colors for other self-defined styles, you can use css variables, but you need to define the variables on root in advance. I use vue Two-way binding binds the theme color to :style="{'--color': defaultTheme}" (see the code above for specific usage), so that the theme can be used under any component Colored (Note: This method is not supported by IE browser), the code is as follows:

<div class="box"></div>
.box {<!-- -->
  width: 100px;
  height: 100px;
  background-color: var(--color);
}

Tip:

  1. If the scss file exports an empty object, you need to change the scss file name to the form of xxx.module.scss, CSS Modules.
  2. If you need to cache chalkCss, \ in css string will be escaped. Remember to manually change it to \. Otherwise, the icon that comes with ElementUI cannot be displayed.

3. Finally

I code each article word by word, and I hope you guys can give me more opinions. Let’s do a three-click combo, likeCollectFollow. Creation is not easy, cheer me up, come on?