Perfect solution for theme switching based on Css Variable (recommended)

Perfect solution for theme switching based on Css Variable (recommended)

When receiving this requirement, Baidu found many solutions for theme switching in the industry, including css link replacement, className change, less.modifyVars, css in js, etc., but each solution sounded tiring and expensive. Is there a solution that has low code intrusion, is easy to use and maintain? Of course there is. To be more precise, CSS itself supports it.

Css3 Variable

Define a global color variable. When you change the value of this variable, all elements that reference this variable in the page will change. Very simple, isn’t it?

// base.less
:root {
  --primary: green;
  --warning: yellow;
  --info: white;
  --danger: red;
}

// var.less
@primary: var(--primary)
@danger: var(--danger)
@info: var(--info)

// page.less
.header {
  background-color: @primary;
  color: @info;
}
.content {
  border: 1px solid @danger;
}
//change.js
function changeTheme(themeObj) {
  const vars = Object.keys(themeObj).map(key => `--${key}:${themeObj[key]}`).join(';')
  document.documentElement.setAttribute('style', vars)
}

End of this article

Fuck, it doesn't support IE ! ! Will it still be compatible with IE in 2020? Yes, it has to be compatible with IE.

css vars ponyfill

Yes, there is a polyfill that is compatible with IE: css-vars-ponyfill. This is how it handles IE

+-------------------------+
| Get the content of the style tag in the page |
| Request external link css content |
+-------------------------+
|
|
v
+-------------------------+ Yes +-------------------------+
| Does the content contain var() | ----> | Marked as src |
+-------------------------+ +-------------------------+
| |
| No |
v v
+-------------------------+ +-------------------------+
| Mark as skip | | Replace var(*) with variable value, |
| | | Add new style tag to head |
+-------------------------+ +-------------------------+

The effect is probably like this

Simple and crude yet elegant, it will not be processed in browsers that support css var, so there is no need to worry about performance issues ( it is IE's problem, not my problem)

). Let's modify the code

//store/theme.js
import cssVars from 'css-vars-ponyfill'

export default {
  state: {
    'primary': 'green',
    'danger': 'white'
  },
  mutations:
    UPDATE_THEME(state, payload) {
      const variables = {}
      Object.assign(state, payload)
      Object.keys(state).forEach((key) => {
        variables[`--${key}`] = state[key]
      })
      cssVars({
        variables
      })
    }
  },
  actions: {
    changeTheme({ commit }, theme = {}) {
      commit('UPDATE_THEME', theme)
    }
  }
}

// router.js
// Because the page after the route jump will load new CSS resources on demand, re-convert const convertedPages = new Set()
router.afterEach((to) => {
  if (convertedPages.has(to.path)) return
  convertedPages.add(to.path)
  context.store.dispatch('theme/changeTheme')
})

SSR project flash screen problem optimization

In SSR projects, you may see this in IE using the above solution

Because css-vars-ponyfill

It relies on DOM elements to achieve conversion and cannot be used in node, so there is a style gap between the server directly outputting the unconverted CSS code and the client loading the JS file to convert the CSS.

+- - - - - - - - - - - - - - - - - - - - - +
'Style window period: '
' '
+----------+ ' +----------------+ +------------+ ' +-------------+
| Initiate request | --> ' | SSR direct output page | --> | Load js dependency | ' --> | Replace css variables |
+----------+ ' +----------------+ +------------+ ' +-------------+
' '
+- - - - - - - - - - - - - - - - - - - - - +

Solving this problem is also very simple. Just add a compatible writing method to each place where css var is used.

@_primary: red
@primary: var(--primary)

:root{
  --primary: @_primary
}

.theme {
  color: @primary;
}

//Change to .theme {
  color: @_primary;
  color: @primary;
}

On browsers that do not support css var, the default color red will be rendered, and ponyfill will replace the style override after js is loaded.

Webpack plugin development

Manually adding compatible writing in each place is both laborious and difficult to maintain. At this time, we need to understand some knowledge related to the webpack life cycle and plug-in development. We can write a webpack plug-in by hand and add a loader to all css modules in the hooks of normalModuleLoader ( v5 version is deprecated, use NormalModule.getCompilationHooks(compilation).loader ) to handle compatible code.

The author's project uses less. Note that the execution order of loaders in webpack is similar to the first-in-last-out order of a stack , so I need to add the conversion loader before the less-loader to ensure that we are dealing with the compiled css var writing rather than the less variable.

// plugin.js
export default class HackCss {
  constructor (theme = {}) {
    this.themeVars = theme
  }

  apply(compiler) {
        compiler.hooks.thisCompilation.tap('HackCss', (compilation) => {
          compilation.hooks.normalModuleLoader.tap(
            'HackCss',
            (_, moduleContext) => {
              if (/\.vue\?vue&type=style/.test(moduleContext.userRequest)) {
                // There will be 2 compilers for ssr project isomorphism. If there is a loader in the module, it will not be added if (hasLoader(moduleContext.loaders, 'hackcss-loader.js')) {
                  return
                }

                let lessLoaderIndex = 0
                // The project uses less, find the location of less-loadermoduleContext.loaders.forEach((loader, index) => {
                  if (/less-loader/.test(loader.loader)) {
                    lessLoaderIndex = index
                  }
                })
  
                moduleContext.loaders.splice(lessLoaderIndex, 0, {
                  loader: path.resolve(__dirname, 'hackcss-loader.js'),
                  options: this.themeVars
                })
              }
            }
          )
        })
      }
    })
}

// loader.js
const { getOptions } = require('loader-utils')

module.exports = function(source) {
  if (/module\.exports/.test(source)) return source
  const theme = getOptions(this) || {}
  return source.replace(
    /\n(.+)?var\(--(.+)?\)(.+)?;/g,
    (content, before, name, after = '') => {
      const [key, indent] = before.split(':')
      const add = after.split(';')[0]
      return `\n${key}:${indent}${theme[name]}${after}${add};${content}`
    }
  )
}

At this point, we can switch themes happily and freely.

postscript

It is more interesting to absorb new knowledge by learning how to be “too lazy to write more code”. I hope this article can help you.

This is the end of this article about the perfect solution for theme switching based on Css Variable (recommended). For more relevant css Variable theme switching content, please search for previous articles on 123WORDPRESS.COM or continue to browse the related articles below. I hope everyone will support 123WORDPRESS.COM in the future!

<<:  Analysis of several reasons why Iframe should be used less

>>:  Detailed explanation of obtaining, assigning, and registering radio values ​​in HTML

Recommend

Setting up shared folders in Ubuntu virtual machine of VMWare14.0.0

This is my first blog post. Due to time constrain...

Docker memory monitoring and stress testing methods

The Docker container that has been running shows ...

How to use js to communicate between two html windows

Scenario: When page A opens page B, after operati...

React realizes secondary linkage (left and right linkage)

This article shares the specific code of React to...

Vue implements card flip carousel display

Vue card flip carousel display, while switching d...

This article teaches you how to play with CSS combination selectors

CSS combination selectors include various combina...

10 very good CSS skills collection and sharing

Here, clever use of CSS techniques allows you to g...

MySQL slow log online problems and optimization solutions

MySQL slow log is a type of information that MySQ...

The difference between the four file extensions .html, .htm, .shtml and .shtm

Many friends who have just started to make web pag...

Using Vue to implement timer function

This article example shares the specific code of ...

HTML table tag tutorial (33): cell vertical alignment attribute VALIGN

In the vertical direction, you can set the cell a...

Fixed table width table-layout: fixed

In order to make the table fill the screen (the re...

JavaScript to achieve calendar effect

This article shares the specific code for JavaScr...