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

How to uninstall and reinstall Tomcat (with pictures and text)

Uninstall tomcat9 1. Since the installation of To...

How to implement responsive layout in vue-cli

When we are doing front-end development, we will ...

MySQL 8.0.12 installation and configuration method graphic tutorial

Record the installation and configuration method ...

Vue implements automatic jump to login page when token expires

The project was tested these days, and the tester...

Tutorial on logging into MySQL after installing Mysql 5.7.17

The installation of mysql-5.7.17 is introduced be...

Some points on using standard HTML codes in web page creation

<br />The most common mistake made by many w...

How to implement insert if none and update if yes in MySql

summary In some scenarios, there may be such a re...

Use pure CSS to disable the a tag in HTML without JavaScript

In fact, this problem has already popped up when I...

How to monitor multiple JVM processes in Zabbix

1. Scenario description: Our environment uses mic...

Get the IP and host name of all hosts on Zabbix

zabbix Zabbix ([`zæbiks]) is an enterprise-level ...

Detailed explanation of Mysql transaction processing

1. MySQL transaction concept MySQL transactions a...

5 Simple XHTML Web Forms for Web Design

Simple XHTML web form in web design 5. Technique ...

Win2008 R2 mysql 5.5 zip format mysql installation and configuration

Win2008 R2 zip format mysql installation and conf...

4 ways to modify MySQL root password (summary)

Method 1: Use the SET PASSWORD command First log ...