With the increasing demand for embedding Ali data pages, some demanding business parties have emerged, hoping to specify the theme color of Ali data to be consistent with their brand color and make the product have a better experience. So we started the research on the dynamic theme function. We thought it was a relatively simple task, but actually there were some twists and turns. However, the final result was quite satisfactory, which met our needs, and was very simple and lightweight.

I. Existing programs

Before starting, I investigated a wave of alternative schemes. The existing schemes are generally of two types: one is that there are multiple preset themes at the time of compilation and construction, only style switching is done at runtime, and a new theme needs to be rebuilt and released; The other is that the runtime can specify any topic, and adding a new class of topics costs almost nothing and is easy to expand.

1. Compile multiple preset themes

The Class switch

An example of compiled CSS is shown below, which switches the theme by giving the body a theme-light or theme-dark class. Less/Sass can be used to facilitate batch processing, but the disadvantages are obvious: CSS file sizes can double.

// index.css
.theme-light div {
    color: # 000;
}

.theme-dark div {
    color: #fff;
}
Copy the code

Build multiple CSS

The drawbacks of class switching can be solved by building multiple CSS files, such as our less code:

div {
    color: @primary-color;
}
Copy the code

The construction phase then specifies @primary-color as a different color value to get multiple CSS artifacts:

// index-light.css
.div {
    color: # 000;
}
Copy the code
// index-dark.css
div {
    color: #fff;
}
Copy the code

CSS and index-dark. CSS are toggled during runtime. But this solution is also unfriendly, requiring intrusion into the build configuration, which then increases the build time.

In the compilation stage preset more than a theme of the scheme, the implementation is relatively simple, the principle is also very well understood, but there are obvious disadvantages, and expand the new theme will have costs.

2. Specify any topic at run time

If you want to extend the theme inexpensively, you must leverage the capabilities of the runtime, which can be implemented in one of three ways, from difficult to easy:

CSS template + runtime replacement

Elemental-ui uses this solution. When switching colors, it passes the color value to the back end to create a new CSS file:

Of course, the work of pure front-end can also be done, the program is quite good.

Less variable

This solution uses the runtime capabilities of less. Js to achieve similar effects to the previous solution, where the CSS template becomes a less file and the runtime substitution is provided by modifyVars of less. It looks something like this:

  • HTML main document adds the project’s LESS file andless.js
<link rel="stylesheet/less" type="text/css" href="/styles.less" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
Copy the code
  • usemodifyVarsModify color variables
window.less.modifyVars({
  '@primary-color': '#0035ff'
})
Copy the code

This scheme has many disadvantages. One is to introduce 40KB less-.js runtime; The second is that coding and packaging are intrusive. We need to add all less files to HTML. Third, theme switching is not smooth, because modifyVars involves recompiling less files.

CSS variable

CSS variables are a new feature of the CSS3 standard, and it is easy to make themes with them. For example, here are our styles:

div{// use --primary-colorThe color of the variable, default if no value# 000
    color: var(--primary-color, # 000); 
}
Copy the code

To switch themes, simply set the new color value through the Document API

document.body.style.setProperty('--primary-color'.'#fff')
Copy the code

This feels like the simplest solution of all, powerful and easy to understand.

There is also a similar STYLEShad-Components CSS in JS scheme, but it changes the project too much, and specifying the theme of the ANTD component would be cumbersome and can be ignored.

Second, our choice

Compiling multiple preset theme types was first killed by us. We have high requirements for low-cost development, otherwise if the business side changes the theme color, we have to follow the release.

The CSS variable scheme has the lowest cost and:

  • Mainstream browsers are supported, and 99%+ of our users are Chrome, so compatibility can be ignored.
  • And then we’ve used less ourselves, by putting@primary: #ff6a00to@primary: var(--primary-color, #ff6a00)It is easy to use and does not require a lot of changes to existing LESS files in the project.
  • We found that ANTD already supports CSS variables to dynamically specify themes;

Perfect, so we ended up with the CSS variable scheme.

Three, hands-on practice

1. Modify global.less

Change the value of the less variable to the CSS variable, and place the original value into the default value

- @primary: #ff6a00;
- @primary5: #ff6a000d;
- @primary15: #ff6a0026;
- @primary75: #ff6a00BF;

+ @primary: var(--primary-color, #ff6a00);
+ @primary5: var(--primary-color-5, #ff6a000d);
+ @primary15: var(--primary-color-15, #ff6a0026);
+ @primary75: var(--primary-color-75, #ff6a00BF);
Copy the code

Local run, no problem, color is normal

2. Modify CSS variables

We added a theme. Ts file that executes its setupTheme in the entry to use the theme color specified on the URL argument.

// theme.ts
import { getUrlParams } from '@/utils/utils';

export let primaryColor = '#ff6a00';
export let primaryColor5 = '#ff6a000d';
export let primaryColor15 = '#ff6a0026';
export let primaryColor75 = '#ff6a00BF';

export function setupTheme() {
  const params = getUrlParams() as any;
  if(params? .primaryColor) { primaryColor =` #${params? .primaryColor.toLocaleLowerCase()}`;
    primaryColor5 = `${primaryColor}0d`;
    primaryColor15 = `${primaryColor}25 `;
    primaryColor75 = `${primaryColor}BF`;
  }
  document.body.style? .setProperty('--primary-color', primaryColor);
  document.body.style? .setProperty('--primary-color-5', primaryColor5);
  document.body.style? .setProperty('--primary-color-15', primaryColor15);
  document.body.style? .setProperty('--primary-color-75', primaryColor75);
}
Copy the code

Local run, specify the primaryColor parameter to other color values, except antD series components are already in effect

3. Specify the ANTD component theme

Follow documentation guidelines, we upgrade the antd 4.17.1 – alpha. 1 version of the import antd/dist/antd variable. Min. CSS, then in the theme. The ts in new topic ConfigProvider to set the color, refresh the page, Antd series components are also in effect, perfect! Package to pre-send feel under…

Only part of antD component themes took effect, and some components, such as pager, did not take effect. Debugging found that ANTD styles were redundant, and CSS variable versions of some components were overwritten. Our UMi.css looks like this:

// The style of the CSS variable version is overwritten by the following, causing the specified theme color to not take effect.ant-pagination-item-active {
    border-color: var(--ant-primary-color);
}

.ant-pagination-item-active {
    border-color: #ff6a00;
}
Copy the code

My first reaction was to modify the CSS order and put the styles of CSS variable version behind. After a lot of trouble, I found that the CSS order of Webpack was not the same as the order of import. Finally, when I saw the issue, I decided to give up.

4. Turn off antD loading on demand

As written in antD’s documentation, ANTD dynamic themes need to be closed for loading on demand:

Note: If you use babel-plugin-import, you need to remove it.

It seems to be the only way. According to the UMI document, @umijs/ plugin-ANTd will introduce antD on demand. After looking through the source code, it cannot be configured and closed at present, so we proposed a PR and talked with Shixian privately.

Then we update @umijs/ preth-react to add disableBabelPluginImport in.umirc.ts to disable on-demand loading.

antd: {
   disableBabelPluginImport: true
}
Copy the code

Should be stable, sent to pre-send to feel again…

The pager is fine, but the button is broken. It is found that the CSS variable styles are overwritten due to redundant styles. It is finally traced to @ant-Design/Pro-Layout

5. Specify a Pro-Layout theme

Reason is pro – layou have reference lib | es less files in the directory, press antd document, need injection in less @ root – an entry – name: variable variables in. Umirc. Ts configured in the following:

theme: {
   'root-entry-name': 'variable'
}
Copy the code

Once again to pre-send, OK, all ready!

Note that ‘primary-color’ cannot be configured after ‘root-entry-name’: ‘variable’ is configured in the theme

Four,

It can be seen that the dynamic theme scheme based on CSS variables is relatively simple. For the projects already using less, the access cost is very low, and the scheme is light and easy to expand. Antd has also given official support to further reduce the use cost, and I believe that it will become the choice of more people in the future.

Develop reading: variables CSS tutorial: www.ruanyifeng.com/blog/2017/0… Element-ui skin remover: github.com/ElemeFE/ele… Talk about the front-end avi: segmentfault.com/a/119000001…