4

demo online experience address: v-theme-colors
Source address: v-theme-colors (most functions of ps have not been released simultaneously)

1. Skin replacement

The function of one-click theme switching (referred to as: skinning) for websites or applications is very common for every front-end developer, usually one deep and one shallow, or free combination to derive many themes, or any theme. At this time, It is particularly important to design an engineered theme switching function and sort out a solution for modern front-end styles.

Second, skin replacement research

A long time ago, it was common practice to write a stylesheet for each color theme block, and switch accordingly when switching. Modern front-end theme switching - the current mainstream solutions are often implemented through CSS variables (CSS custom properties) , and theme-related colors are named in business and semantic ways. Next, let's take a look at how the well-known UIs in the front-end circle do:

(1) ElementUI

image.png
We can see the element-plus official website, the theme switching theme, is in the html tag plus class="dark"
Variables related to themes, based on html.dark, change with :root. Of course, we can also see through the source code that element-plus uses scss.

image.png

(2) ant.design

We can see that on the ant.design official website, the theme switching theme is in the html tag Garry color-scheme and adding a custom tag in the body data-theme="dark" , and :root to cooperate with the change. CSS properties allow an element to indicate the color scheme it can easily render in. Common choices for operating system color schemes are "light" and "dark", or "day mode" and "night mode". When the user selects one of the color schemes, the operating system makes adjustments to the user interface. This includes the use of values for form controls, scroll bars, and CSS system colors. Through the source code, we can also see that ant uses less.

image.png

3. Peeling pain points and thinking

(1) As mentioned above, ElementUI and ant use different CSS preprocessors (sass and less) to organize code. In the era of micro front-end, how to design a set of general multi-team available, and go to CSS preprocessing A skinnable CSS custom property?

(2) Who will maintain different theme colors, and how to maintain the synchronous communication of different theme color values between R&D and design?

(3) How to minimize the development volume of front-end engineers without having to do multiple theme colors?
(4) ...

Based on the above considerations, for example, we hope to write:

 .text {
  color: var(--c-color)
}

This can be done once and for all - directly support two or more sets of theme modes.

But business is often ever-changing, as our company:

(1) The need for skin peeling is that there is a tone (shade), and then, according to the shade, many theme colors are derived, such as dark blue, dark yellow, dark red, light blue, light yellow, light red...

(2) For shades, some basic colors are determined. For component colors, a set of basic colors is usually sufficient, but business pages may involve ever-changing colors...

4. Skinning Architecture

image.png

As shown above, we can upgrade skinning to a platform or middle platform:

(1) For UED students, they can configure the system color system related to skinning, the derived theme color, and the base color of the system color system.

(2) For each front-end team, you can customize and configure the variables of the color required by your business through the theme color, the basic color of the color system, and the

image.png

5. Selection and implementation of skin peeling technology

The author uses a skinning solution based on css-vars-ponyfill here. As for its advantages, as they are officially described, it provides client-side supported pnyfill for css custom properties in traditional and modern browsers.

image.png

image.png

[1] Highlights and rules of the program

(1) Pure JS implementation, exposing the initThemes initialization method to the outside world, not relying on CSS preprocessors (sass and less), compatible with ie9

(2) The base color of the dark and light color system (unified management output), as well as the theme color and mixed color (black and white) can be obtained through the dynamic interface

(3) Unify the naming of business color constants, JS defines custom function methods 1. The Mix function implements a color mixing mechanism comparable to sass , 2. Hexadecimal and RGB (rgba) conversion functions

(4) The technical route does not shake, use the var() function directly, encapsulate it into a JS library in the later stage, and configure the skin in the middle stage, which can be provided to each team for use

(5) Regarding business-defined variables, there are two governance schemes in the design: (1) Global variables, which are maintained globally independently (2) Local business variables, which are maintained locally and independently

【2】Core principle

(1) When the skinning operation is triggered on the application side, cooperate with JavaScript state management to synchronize the signal of theme switching, corresponding to triggering the initThemes method

 // 测试新主题
  let varList = {
    ...colorColor
  }
  let tPrimaryList = themePrimaryList
  initThemes('', tPrimaryList, varList, '')

(2) Switch the theme color or even the value corresponding to the variable under the business, and type the custom constant to the corresponding DOM node (usually under html or body) through css-vars-ponyfill, so as to switch the theme

theme.js

 import cssVars from "css-vars-ponyfill";
import { themeTypeList, themePrimaryList } from "./themeList.js";
import { mix, hex2rgb } from "./com/util";
/**
 * initThemes 全局初始化 主题
 * @param theme 主题 [必填]
 * @param tPrimaryList 主题列表[必填] array ['theme1','theme2']
 * @param valList 自定义主题列表 {val1:['theme1-color','theme2-color']} ....
 * @param themeType 主题类型  -深浅 ....
 * @param changeType 区分改的是主题类型,还是主题色 [ 预留字段 ]....
 * @returns {boolean}
 */
 
export const initThemes = (theme, tPrimaryList, varList, themeType) => {
  let variables = getVariables(
    theme || "lightBlue",
    tPrimaryList,
    varList,
    themeType
  );

  cssVars({
    watch: true, // 当添加,删除或修改其或元素的禁用或href属性时,ponyfill将自行调用
    variables: variables, // variables 自定义属性名/值对的集合
    onlyLegacy: false, // false  默认将css变量编译为浏览器识别的css样式  true 当浏览器不支持css变量的时候将css变量编译为识别的css
  });
};

themeList.js Here are some themes and colors (dark and light) base colors that are assumed to be set on the application side

 import { light } from './com/light'
import { dark } from './com/dark'

// 主题 - 主题色
export const themePrimaryList = {
  dark: [
    {
      color: '#FFAA0E',
      name: '深黄',
      theme: 'darkYellow'
    },
    {
      color: '#FFAA0E',
      name: '深蓝',
      theme: 'darkBlue'
    },
  ],
  light: [
    {
      color: '#FFAA0E',
      name: '深黄',
      theme: 'lightYellow'
    },
    {
      color: '#256DFF',
      name: '浅蓝',
      theme: 'lightBlue'
    }
  ]
}

export const themeTypeList = {
  dark: dark,
  light: light,
}

【3】Color group & color value platform design

For front-end users, we only need to pay attention to what constants are there and how to define them. For example, in our company, we define some common semantic business constants based on business, such as

 // 功能色
    "--c-primary": color.C00, // 主题色
    "--c-primary-rgb": hex2rgb(color.C00), // 主题色RGB
    "--c-primary-hover": mix(white, color.C00, 12),
    "--c-primary-active": mix(black, color.C00, 12),
    "--c-fill-primary": mix(white, color.C00, 88), //主题色文字的背景填充色
    "--c-border-primary": mix(white, color.C00, 80), //主题色文字的边框色
    "--c-primary-mix-1": mix(white, color.C00, 10),
    "--c-primary-mix-2": mix(white, color.C00, 20),
    "--c-primary-mix-3": mix(white, color.C00, 30),
    "--c-primary-mix-4": mix(white, color.C00, 40),
    "--c-primary-mix-5": mix(white, color.C00, 50),
    "--c-primary-mix-6": mix(white, color.C00, 60),
    "--c-primary-mix-7": mix(white, color.C00, 70),
    "--c-primary-mix-8": mix(white, color.C00, 80),
    "--c-primary-mix-9": mix(white, color.C00, 90),

    "--c-success": color.C01, // 成功色
    "--c-warning": color.C01, // 警告色
    "--c-error": color.C01, // 错误色

    "--c-green": color.C06, // 语义绿 跌
    "--c-green-rgb": hex2rgb(color.C06),
    "--c-green-hover": mix(white, color.C06, 12),
    "--c-green-active": mix(black, color.C06, 12),

    "--c-red": color.C08, // 语义红 涨
    "--c-red-rgb": hex2rgb(color.C08),
    "--c-red-hover": mix(white, color.C08, 12),
    "--c-red-active": mix(black, color.C08, 12),

    "--c-yellow": color.C07, // 语义黄
    "--c-yellow-rgb": hex2rgb(color.C07),
    "--c-yellow-hover": mix(white, color.C07, 12),
    "--c-yellow-active": mix(black, color.C07, 12),

    // 文字色
    "--c-text": color.C02, // 一般文本
    "--c-text-title": color.C02, // 标题
    "--c-text-subtitle": hex2rgb(color.C02, 0.65), // 次要 - 副标题
    "--c-text-info": hex2rgb(color.C02, 0.45), // 提示
    "--c-text-placeholder": color.C02, // 占位文本色
    "--c-text-link": color.C01, // 链接文本色
    "--c-text-disable": hex2rgb(color.C02, 0.3), // 禁止或失效

    // 填充色
    "--c-fill": color.C04, // 组件默认背景颜色
    "--c-fill-body": color.C11, // 页面背景
    "--c-fill-shadow": hex2rgb(color.C11, 0.1), // 阴影
    "--c-fill-zebra": color.C05, // 斑马线色
    "--c-fill-mask": color.C11, // 遮罩背景
    "--c-fill-disable": hex2rgb(color.C02, 0.07), // 禁止

    "--c-fill-scroll": hex2rgb(color.C02, 0.5), // 滚动条色
    "--c-fill-scroll-hover": mix(white, color.C02, 12),
    "--c-fill-scroll-active": mix(black, color.C02, 12),

    // 边框/分割线颜色
    "--c-border": color.C01, // 基本边框色
    "--c-border-line": hex2rgb(color.C02, 0.07), // 分割线
    "--c-border-light": hex2rgb(color.C02, 0.12), // 浅边框色 (小边框)
    "--c-border-lighter": hex2rgb(color.C02, 0.07), // 更浅色
    "--c-border-disable": hex2rgb(color.C02, 0.04), // 禁用边框

    // 图标色
    "--c-icon": hex2rgb(color.C02, 0.65),
    "--c-icon-hover": color.C09,
    "--c-icon-active": color.C01,
    "--c-icon-down": color.C10,

    // 标签色
    // ...getTabColor(color),
    
    // 业务自定义- 写在业务方变量
    //由主题切换的时候,动态传入入的 自定义变量列表 varList
    ...getBusinessVars(theme, type, varList, tPrimaryList),

When using it, you only need to be familiar with these semantic constants. Of course, we have also designed a visualization page, you can see the full amount of custom variables and the corresponding colors, which is more convenient for global viewing.

Of course, as for the above mixed code, you may look a little strange. This is because our UED classmates have designed a set of color specifications to reduce the color (for example, the floating color is calculated based on the mixture of 8.8 as the default color and 1.2 as white; press The lower color is calculated by mixing 8.8 as the default color and 1.2 as black), such as the Mix function (the color mixing rules conform to scss-mix), and the rest are functions such as RGB and hexadecimal color conversion.

[4] Get the color of the custom variable in the current theme

Customized variable colors: For businesses, the basic colors may not meet the color coverage of all businesses, or the basic colors under each theme cannot correspond one-to-one. At this time, the function of customizing variable colors becomes indispensable. The main principle is that you can fill in the color of the corresponding business needs (extreme cases) according to each theme. There are also two scenarios here:

(1) Fully custom constant polymorphism, that is, a theme color custom constant has a corresponding color

For example, there are four system themes [dark1,dark2,light1,light2] , a business background color, we define a constant --color-codercao-fill01 it is in:
dark1 The color under the theme is a base color dark.C01 ;
dark2 The color under the theme is a base color dark.C02 ;
light1 The color under the theme is a base color light.C03 ;
light2 The color under the theme is #fff ;

At this moment, this situation is a relatively extreme situation. Under each theme, our custom constants correspond to four themes with four colors, and the colors are irregular. It may be the basic color or any color.
--color-codercao-fill01 :['dark.C01','dark.C02','light.C03','#fff']

(2) Only the custom constant polymorphism related to the shade, only the color related to the shade base color

For example, there are four system themes [dark1,dark2,light1,light2] , a business background color, we define a constant --color-codercao-fill02 It is in:
dark1 and dark2 the color under the dark theme is a base color #555 ;
light1 and light2 light theme color is a base color #fff ;

At the moment, in this case, our custom constants correspond to the four themes and there are only two colors, these two may be basic colors or arbitrary colors.
--color-codercao-fill02 :['#555','#fff']

 // 主题变量color.vue
    // (1)完全自定义常量多态,一种主题色对应一种颜色
    "--color-codercao-test0": mergeColor(
      ["#444", "#666"],
      ["#444", "#666"],
      type,
      theme,
      tPrimaryList
    ), 
    // (2)只和深浅相关自定义常量多态,只和深浅基础色有关的颜色
    "--color-codercao-test1": mergeColor([dark.C01], [light.C02], type),

We need to calculate dynamically according to the theme switching, so we need to design a method to calculate the color, which gets the color, theme, theme list, and even theme type under each theme to calculate the variable under the current theme. Which color to use

 /**
 * mergeColor 获取在当前主题下该变量(自定义)的颜色
 * @param darkList [必填]  自定义常量在不同主题下的 深色系颜色列表   array ['theme1','theme2']
 * @param lightList [必填]  自定义常量在不同主题下的 浅色系颜色列表   array ['theme1','theme2']
 * @param type 主题类型  深 - 浅
 * @param theme 主题色-名
 * @param tPrimaryList 主题列表
 * @returns {boolean}
 */

const mergeColor = (darkList, lightList, type, theme, tPrimaryList) => {
  let colorList = type == "dark" ? darkList : lightList;
  let color = colorList[0],
    index = 0;
  // 如果 type 有值说明 该自定义主题常量,只和深浅基础色 (两种)有关
  if (!theme) {
    color = type == "dark" ? darkList[0] : lightList[0];
  } else {
    // 否则认为是 一种主题 一种色值
    index = getThemeIndex(theme, tPrimaryList);
    color = colorList[index];
  }
  return color;
};

In addition, there is an extreme case where css variables can be customized on the corresponding DOM.

Then we can have fun changing skins~

color.gif

6. Summary

This skinning solution is based on the css-vars-ponyfill plug-in to write the core skinning function in pure JS, without relying on css preprocessing, mainly by highly extracting the basic color, and then converting it into a more semantic business (function) variable color, and then With the theme, change the specific color accordingly, and reserve the function of custom variables to make the skin change more soulful. Of course, if you have better ideas, you can explore and share them together in the comment area.

demo online experience address: v-theme-colors
Source address: v-theme-colors (most functions of ps have not been released simultaneously)


codercao
395 声望19 粉丝