Several Solutions for Front-end "One-click Skinning"

Today, let's take a look at several solutions for front-end "one-click skinning".

CSS Style Override Implementation

Switching theme styles by switching CSS selectors.

Keep the same style in the component, and extract the style that needs to be changed.

Provide a variety of styles, and define a corresponding CSS selector for different themes.

Set different styles according to different themes.

Example

The global theme color is stored and controlled by vuex below. The sample code is as follows.

import { createStore } from 'vuex'

// Create a new store instance
const store = createStore({
   state() {
     return {
       theme: 'light'
     }
   },
   mutations: {
     setTheme (state, payload) {
       state.theme = payload
       document.querySelector('body').className = payload
     }
   }
})

export default store

In the template, set the corresponding class name through the theme in vuex, for example, the header code is as follows.

<template>
  <div :class="['header', store.state.theme]">
    <span>{{title}}</span>
    <input v-model="checked" type="checkbox" class="switch" @change="changeTheme" />
  </div>
</template>

In theme.css, the light and dark themes are distinguished by the .light and .dark class selectors, and their corresponding styles are prepared in advance. Examples are as follows.

/* Light default theme */
body.light {
   background-color: #fff;
}

.header.light {
   background-color: #fff;
   border-bottom: 1px solid #d6d6d6;
   color: rgb(51, 50, 50);
}

.list.light .title {
   color: rgb(51, 50, 50);
}
.list.light .describe{
   color: rgb(158, 158, 158);
}

.list.light .left{
   border: 1px solid rgb(51, 50, 50);
}

/* Dark dark theme */
body.dark {
   background-color: rgb(51, 50, 50);
}

.header.dark {
   background-color: rgb(51, 50, 50);
   border-bottom: 1px solid #fff;
   color: #fff;
}

.list.dark .title {
   color: #fff;
}
.list.dark .describe{
   color: rgb(201, 201, 201);
}
.list.dark .left{
   border: 1px solid #fff;
   background-color: #fff;
}

Disadvantages

A variety of theme styles must be introduced, resulting in an increase in the amount of code.

Styles are not easy to manage.

Find styles are complex.

Development efficiency is low.

Poor scalability.

Implement Multiple Sets of CSS Theme Styles

According to the user's switching operation, different theme styles are dynamically loaded through the link tag, which mainly solves the problem that multiple theme colors are compiled into one file, which causes a single file to be too large.

Example

The CSS part is directly split into two files, ligth.css, and dark.css. The setTheme.js code for setting the theme section is as follows.

export default function setTheme(theme = 'ligth') {
  let link = document.querySelector('#theme-link')
  let href = "/theme/" + theme + ".css"
  
  if (!link) {
    let head = document.querySelector('head')
    link = document.createElement('link')
    link.id = '#theme-link'
    link.rel = "stylesheet"
    link.href = href
    head.appendChild(link)
  } else {
    link.href = href
  }
}

Disadvantages

Need to repeat CV multiple style files for individual modification.

The variable style part is not extracted separately.

You need to know the packaged file path in advance, otherwise, it may cause errors in the theme style introduction.

CSS Variable Implementation

Dynamically modify the CSS variables on the body through body.style.setProperty(key, value), so that other parts of the page can apply the styles corresponding to the latest CSS variables.

Example

theme.css is responsible for defining global CSS variables. The sample code is as follows.

/* Implementation method one */
:root {
   --theme-bg: initial; // background color
   --theme-color: initial; // font color
   --theme-boder-color: initial; // border color
}

===================================================== ==

/* Implementation 2 */
/* Default: light */
:root {
   --theme-bg: #fff;
   --theme-color: rgb(51, 50, 50);
   --theme-img-bg: #fff;
   --theme-boder-color: #d6d6d6;
}

/* dark: dark */
[>'dark'] {
   --theme-bg: rgb(51, 50, 50);
   --theme-color: #fff;
   --theme-boder-color: #fff;
}

themeUtil.js is responsible for obtaining the current corresponding style value and setting the CSS variable value on the body. The sample code is as follows.

const darkTheme = 'rgb(51, 50, 50)'
const lightTheme = '#fff'
const lightBorderTheme = '#d6d6d6'

// Get the corresponding theme color value
export const getThemeMap = (isLight) => {
   return {
     'theme-bg': isLight ? lightTheme : darkTheme,
     'theme-color': isLight ? darkTheme : lightTheme,
     'theme-boder-color': isLight ? lightBorderTheme : lightTheme,
   }
}

// Set the theme color value
export const setTheme = (isLight = true) => {
   const themeMap = getThemeMap(isLight)
   const body = document.body
   /* Implementation method one */
   Object.keys(themeMap).forEach(key => {
     body.style.setProperty(`--${key}`, themeMap[key])
   })
  
   /* Implementation 2 */
   // body.style.setProperty('data-theme', isLight ? 'light' : 'dark')
}

Use var() to apply the corresponding CSS variable in the component, such as the use in the head.

<style scoped>
.header {
   ...
   color: var(--theme-color);
   border-bottom: 1px solid var(--theme-boder-color);
   background-color: var(--theme-bg);
}
...
</style>

Disadvantages

The disadvantage is that the compatibility is not good. We can handle CSS variables compatible with css-vars-ponyfill. The code changes in themeUtil.js are as follows.

import cssVars from "css-vars-ponyfill";

const darkTheme = 'rgb(51, 50, 50)'
const lightTheme = '#fff'
const lightBorderTheme = '#d6d6d6'

// The key/value pair defined here is to pass parameters to cssVars
export const getThemeMap = (isLight) => {
  return {
    '--theme-bg': isLight ? lightTheme : darkTheme,
    '--theme-img-bg': lightTheme,
    '--theme-color': isLight ? darkTheme : lightTheme,
    '--theme-boder-color': isLight ? lightBorderTheme : lightTheme,
  }
}

export const setTheme = (isLight = true) => {
  const themeMap = getThemeMap(isLight)
  const body = document.body
  
  /* Implementation method one */
  Object.keys(themeMap).forEach(key => {
    body.style.setProperty(key, themeMap[key])
  })
  
  /* Implementation 2 */
  // body.style.setProperty('data-theme', isLight ? 'light' : 'dark')
  
  // Implement a compatible solution
  cssVars({
    watch: true, // ponyfill will call itself when adding, removing, modifying the disabled or href attributes of <link> or <style> elements
    variables: themeMap, // Variables is a collection of custom attribute name/value pairs
    onlyLegacy: false, // False By default, CSS variables are compiled into CSS styles recognized by the browser; true When the browser does not support CSS variables, CSS variables are compiled into recognized CSS.
  });
}

Theme Image Switching

After implementing the previous content, now add a logo to the light and dark themes respectively. This part is actually very simple. The following sample code is implemented based on Vue3.

// Header.vue
<script setup>
import { ref } from 'vue'
import { setTheme } from '../style/themeUtil'

defineProps({
  title: String
})

const checked = ref(false)

const logoUrl = ref('')

const loadImg = async () => {
  let name = !checked.value ? 'light' : 'dark'
  let ext = !checked.value ? 'png' : 'jpg'
  let res = await import(`../assets/logo-${name}.${ext}`)
  logoUrl.value = res.default
}

loadImg()

const changeTheme = (event) => {
  setTheme(!checked.value)
  loadImg()
}

</script>

<template>
  <div class="header">
    <img class="logo" :src="logoUrl" />
    <span>{{ title }}</span>
    <input v-model="checked" type="checkbox" class="switch" @change="changeTheme" />
  </div>
</template>


Leave a reply



Submit