Demand background

With the development of the business, the needs of customers will become more diversified, and the ability to customize the interface will be required in the later stage of the product, hence the need for “dynamic skin”.

Colleagues in the Design department referred us to the evolution of Ant Design’s palette generation algorithm

Later, we also used the current Ant Design algorithm to dynamically calculate the color palette, @ant-design/colors

However, the way of switching themes proved not to be perfect for our micro front end project.

Design criteria

Light mode variable table

Dark mode variable meter

A color meter with a constant value in both modes

The above color change scale is all the variables we finally need for this time

Among them, each color system is divided into two kinds, starting with H and a. The a color starting with A is generated by adjusting the transparency, and the GROUP starting with H is generated by the base color through the dynamic calculation of Ant-Design

Natural color design is produced by hexi design team, neutral color is directly defined dead, do not calculate;

Configurable base color

  • Brand color: #22B2CC
  • Warning color (warning-base) : #FAAD14
  • Danger color: #F5222D
  • Prompt color (info-base) : #1890FF
  • Success-base: #52C41A

The front-end solutions

After I received the demand, after discussing with the company’s architects and other colleagues, I gradually produced the following solutions, step by step.

Plan a

For both theme modes (light/dark), two separate LESS files are required to define the two sets of color variables

Light-colors.less

dark-colors.less

In both modes, the color variables with fixed values are defined as a single file common-colors.less. Then I choose to import three files into the same index and use index.less where needed.

But here’s the problem

  1. How to determine whether to use light-colors or dark-colors in index.less?

    @import can only be defined at the top of the file, and there are no conditional import methods

  2. How to dynamically calculate the value of color variables according to brand colors?

    To dynamically switch, you need to use the modifyVars method of Less, which is also the official method provided by Ant Design. Let’s try

Scheme 2

The modifyVars method for less is implemented based on compilation of less in the browser. Therefore, when importing less files, you need to import them in link mode, and then modify the variables based on the method in less.js

less.modifyVars({
  '@themeColor': '#22B2CC'
});
Copy the code

Link mode introduces theme color files

<link rel="stylesheet/less" type="text/css" href="./src/less/theme-colors.less" />
Copy the code

Change the theme color event

// color passes the color value
changeTheme (color) {
    less.modifyVars({  // Call the 'less. ModifyVars' method to change the variable value.
         @themeColor':color }) .then(() => { console.log('Modify the success'); }); };Copy the code

You can refer to Ant Design’s official website for guidance on specific usage. There are several problems as follows, which cannot be adapted to our project

  1. Need to introduce less compiler, too big, serious performance impact;

  2. Webpack configuration is required, variables cannot be shared between multiple processes, and is not suitable for microfront-end projects.

  3. This method is only available for projects using less, if your project uses SASS, there is no such solution as less.modifyVars.

Plan 3

  1. At Webpack build time, the webpack-theme-color-replacer plugin extracts theme color styles from all output CSS files and creates a ‘theme-colors.css’ file that contains only color styles. When the web page is running, the client part downloads this CSS file, and then dynamically replaces the color with a new custom color, which can meet the more flexible and rich functional scenes with excellent performance.

  2. Ant-design /colors to dynamically calculate brand and functional colors.

  3. You can dynamically switch brand colors to get the whole theme switch.

Color system Automatically calculates and outputs the set of colors through the provided reference colors:

The entire array of colors can be computed as follows:

Where you need to set the color, you can directly use these variables defined. When you need to switch the theme or color, pass in the theme mode and recalculate the brand color, and you can dynamically switch the theme.

There seems to be no problem, but in our system, there is a problem.

Since we are a micro front end project, unpack about 20 or 30 projects and create a theme-colors. CSS file that contains only the color styles. This step is run at compile time, so each subproject without the webpack will not be able to share this variable and will get an error at compile time! Even if each project is configured with such a Webpack build, it will create its own theme-colors. CSS file and will not be able to switch the theme synchronously when changing the theme.

Thus, even if a program is good and mature, it is not suitable for all projects. When implementing a plan, we should make an analysis according to our own project situation and make a solution in line with our own project is the absolute truth, rather than blindly copying blindly.

So the plan is killed and the next plan continues to be thought of.

Plan 4

Now that Css3 variables are widely supported in browsers, sharing global theme variables based on Css3 Variable seems to be a common solution.

Define a global variable, change the value of the variable, all references to the variable in the page will be changed, there is no less compilation process, there are no performance issues, this is not the most desired dynamic skin solution?

Css3 Variable is used by prefixing variables with –, and changing the themeColor to var(–themeColor)

Let’s check compatibility first

Mainstream browsers are basically all compatible, which is sufficient for most Internet enterprise products, but for some products that still use Internet Explorer, ponyFill solution is compatible.

There is indeed a polyfill that is compatible with IE: CSS-vars-ponyFill

This polyfill will only work in environments that do not support Css3 Variable

Let’s start writing code:

1. Create a JS file storing public CSS variables (variable.js) and store CSS variables to be defined in this JS file. Brand color and function color can be calculated by ANTD algorithm.

import { getAlphaColor } from "./themeUtils";
const { generate } = require("@ant-design/colors");
import baseTheme from "./baseTheme";
import lightTheme from "./lightTheme";
import darkTheme from "./darkTheme";
import { functionalColorsBase, grayBase } from "./colors";

const themeModes = {
  light: undefined.dark: {
    theme: "dark".backgroundColor: grayBase,
  },
};

// Get the brand color
export const getBrandColors = (color, mode) = > {
  let options = themeModes[mode];
  return generate(color, options);
};

// Get the functional color scheme
export const getFunctionalColors = (mode) = > {
  let options = themeModes[mode];
  let { success, warning, danger, info } = functionalColorsBase;
  const successColors = generate(success, options);
  const warningColors = generate(warning, options);
  const dangerColors = generate(danger, options);
  const infoColors = generate(info, options);
  return {
    success: successColors,
    warning: warningColors,
    danger: dangerColors,
    info: infoColors,
  };
};

// Select * from the following table
export const modifyVars = (color, mode) = > {
  const brandColors = getBrandColors(color, mode);
  const { success, warning, danger, info } = getFunctionalColors(mode);
  constcolors = { ... baseTheme,"--brand-base": brandColors[5]."--success-base": success[5]."--warning-base": warning[5]."--danger-base": danger[5]."--info-base": info[5]."--h-brand-1": brandColors[0]."--h-brand-2": brandColors[1]."--h-brand-3": brandColors[2]."--h-brand-4": brandColors[3]."--h-brand-5": brandColors[4]."--h-brand-6": brandColors[5]."--h-brand-7": brandColors[6]."--h-brand-8": brandColors[7]."--h-brand-9": brandColors[8]."--h-brand-10": brandColors[9]."--h-success-1": success[0]."--h-success-2": success[1]."--h-success-3": success[2]."--h-success-4": success[3]."--h-success-5": success[4]."--h-success-6": success[5]."--h-success-7": success[6]."--h-success-8": success[7]."--h-success-9": success[8]."--h-success-10": success[9]."--h-warning-1": warning[0]."--h-warning-2": warning[1]."--h-warning-3": warning[2]."--h-warning-4": warning[3]."--h-warning-5": warning[4]."--h-warning-6": warning[5]."--h-warning-7": warning[6]."--h-warning-8": warning[7]."--h-warning-9": warning[8]."--h-warning-10": warning[9]."--h-danger-1": danger[0]."--h-danger-2": danger[1]."--h-danger-3": danger[2]."--h-danger-4": danger[3]."--h-danger-5": danger[4]."--h-danger-6": danger[5]."--h-danger-7": danger[6]."--h-danger-8": danger[7]."--h-danger-9": danger[8]."--h-danger-10": danger[9]."--h-info-1": info[0]."--h-info-2": info[1]."--h-info-3": info[2]."--h-info-4": info[3]."--h-info-5": info[4]."--h-info-6": info[5]."--h-info-7": info[6]."--h-info-8": info[7]."--h-info-9": info[8]."--h-info-10": info[9]};const darkConfigableTheme = {
    "--a-brand-1": getAlphaColor(brandColors[5].0.04),
    "--a-brand-2": getAlphaColor(brandColors[5].0.08),
    "--a-brand-3": getAlphaColor(brandColors[5].0.16),
    "--a-brand-4": getAlphaColor(brandColors[5].0.24),
    "--a-brand-5": getAlphaColor(brandColors[5].0.32),
    "--a-brand-6": getAlphaColor(brandColors[5].0.4),
    "--a-brand-7": getAlphaColor(brandColors[5].0.52),
    "--a-brand-8": getAlphaColor(brandColors[5].0.64),
    "--a-brand-9": getAlphaColor(brandColors[5].0.76),
    "--a-brand-10": getAlphaColor(brandColors[5].0.88),

    "--a-success-1": getAlphaColor(success[5].0.04),
    "--a-success-2": getAlphaColor(success[5].0.08),
    "--a-success-3": getAlphaColor(success[5].0.16),
    "--a-success-4": getAlphaColor(success[5].0.24),
    "--a-success-5": getAlphaColor(success[5].0.32),
    "--a-success-6": getAlphaColor(success[5].0.4),
    "--a-success-7": getAlphaColor(success[5].0.52),
    "--a-success-8": getAlphaColor(success[5].0.64),
    "--a-success-9": getAlphaColor(success[5].0.76),
    "--a-success-10": getAlphaColor(success[5].0.88),

    "--a-warning-1": getAlphaColor(warning[5].0.04),
    "--a-warning-2": getAlphaColor(warning[5].0.08),
    "--a-warning-3": getAlphaColor(warning[5].0.16),
    "--a-warning-4": getAlphaColor(warning[5].0.24),
    "--a-warning-5": getAlphaColor(warning[5].0.32),
    "--a-warning-6": getAlphaColor(warning[5].0.4),
    "--a-warning-7": getAlphaColor(warning[5].0.52),
    "--a-warning-8": getAlphaColor(warning[5].0.64),
    "--a-warning-9": getAlphaColor(warning[5].0.76),
    "--a-warning-10": getAlphaColor(warning[5].0.88),

    "--a-danger-1": getAlphaColor(danger[5].0.04),
    "--a-danger-2": getAlphaColor(danger[5].0.08),
    "--a-danger-3": getAlphaColor(danger[5].0.16),
    "--a-danger-4": getAlphaColor(danger[5].0.24),
    "--a-danger-5": getAlphaColor(danger[5].0.32),
    "--a-danger-6": getAlphaColor(danger[5].0.4),
    "--a-danger-7": getAlphaColor(danger[5].0.52),
    "--a-danger-8": getAlphaColor(danger[5].0.64),
    "--a-danger-9": getAlphaColor(danger[5].0.76),
    "--a-danger-10": getAlphaColor(danger[5].0.88),

    "--a-info-1": getAlphaColor(info[5].0.04),
    "--a-info-2": getAlphaColor(info[5].0.08),
    "--a-info-3": getAlphaColor(info[5].0.16),
    "--a-info-4": getAlphaColor(info[5].0.24),
    "--a-info-5": getAlphaColor(info[5].0.32),
    "--a-info-6": getAlphaColor(info[5].0.4),
    "--a-info-7": getAlphaColor(info[5].0.52),
    "--a-info-8": getAlphaColor(info[5].0.64),
    "--a-info-9": getAlphaColor(info[5].0.76),
    "--a-info-10": getAlphaColor(info[5].0.88),};constlightModeColors = { ... lightTheme, ... colors };constdarkModeColors = { ... darkTheme, ... darkConfigableTheme, ... colors };console.log(lightModeColors, "= = = = =", darkModeColors);
  return mode == "light" ? lightModeColors : darkModeColors;
};

Copy the code

2, the page uses CSS variables, whether the Web main project, or each plugin sub-project can share variables, without introducing any dependency, design annotation and code corresponding relationship:

UI code
h-brand-1 var(–h-brand-1)

3, package switch theme JS, do the initial call in the project entrance, support to change the light and dark mode, and change the brand color base color

import { brandBase, modifyVars } from "./variable";
import cssVars from "css-vars-ponyfill";

const key = "data-theme";

// Get the current topic
export const getTheme = (mode, color) = > {
  const localTheme = localStorage.getItem(key);
  const dataTheme = localTheme
    ? JSON.parse(localTheme)
    : {
        color: color || brandBase,
        mode: mode || "light"};return dataTheme;
};

// Initialize the topic
export const initTheme = (mode, color) = > {
  const dataTheme = getTheme(mode, color);
  document.documentElement.setAttribute("data-theme", dataTheme.mode);
  cssVars({
    watch: true.Ponyfill is called when the disabled or href attribute of its 
       or 
    variables: modifyVars(dataTheme.color, dataTheme.mode), // variables A collection of custom attribute name/value pairs
    onlyLegacy: false.// false Compiles CSS variables to browser-recognized CSS styles by default. True Compiles CSS variables to browser-recognized CSS styles when the browser does not support CSS variables
  });
};

// Change the theme
export const changeTheme = (mode, color) = > {
  const dataTheme = {
    color: color || brandBase,
    mode: mode || "light"};localStorage.setItem(key, JSON.stringify(dataTheme));
  document.documentElement.setAttribute("data-theme", dataTheme.mode);
  cssVars({
    watch: true.variables: modifyVars(dataTheme.color, dataTheme.mode),
    onlyLegacy: false}); };Copy the code

4. Switch the theme by calling changeTheme in the button component of switch Theme

The final effect is that currently only part of the minesweeper part of the page, the control switch is for the temporary requisition sidebar:

conclusion

At this point, a micro front-end project dynamic skin scheme has been realized, if you have a better scheme, welcome to add oh ~

Note: this project comes from the front-end team of Hesida, which has technical teams in Beijing and Nanchang. If you are considering new job opportunities, welcome to submit your resume!

Directly follow the public account “1 minute front”, send the message “interview”, or send your resume to [email protected], marked “from nuggets” email