自定义主题色

828 阅读3分钟

方案1:less 浏览器端编译

原理

利用 less 在浏览器端实时编译的原理,在项目初始化和更改主题色时,调用 less.modifyVars() 方法,实现主题色的变更。

用法

  • less.js 在浏览器端使用:
<link rel="stylesheet/less" type="text/css" href="color.less" />
<script>
  window.less = {
    env: "development",
    async: false
  };
</script>
<script src="less.js" type="text/javascript"></script>
  • 更改主题色
window.less.pageLoadFinished.then(() => {
  window.less.modifyVars({
    '@maycur-color': color,
    '@primary-color': color,
    '@brand-primary': color,
    '@brand-primary-tap': color
  }).then(() => {
    // eslint-disable-next-line no-console
    console.log('theme generate successfully');
  });
});

项目落地步骤

  • 安装 @maycur/theme-webpack-plugin, 这一插件主要是在 webpack 打包阶段,在将静态资源输出到 output 目录之前,改变 index.html 的内容,在其 标签后面插入上述第一段代码(用法 - less.js 在浏览器端的使用),并生成 color.less 文件
  • 将主题色相关的变量提出到 color.less 中
// @import "~maycur-antd/lib/style/themes/default.less";
// @import "~@maycur/business/lib/style/variables.less";
@import "colorPalette";

@maycur-color: #0670FF;
@maycur-color-1: color(~`colorPalette('@{maycur-color}', 1)`);
@maycur-color-2: color(~`colorPalette('@{maycur-color}', 2)`);

/**
 * antd-mobile color
 */
@brand-primary: @maycur-color;
@brand-primary-tap: @maycur-color;
@ghost-button-fill-tap: color(~`colorPalette('@{maycur-color}', 3)`);;
  • @maycur/theme-webpack-plugin 插件配置
// themeOptions.js
const fs = require('fs');
const path = require('path');
const utils = require('./utils.js');

const lessPath = path.join(__dirname, '../src/less/themes/color/color.less');

module.exports = {
  antdStylesDir: path.join(__dirname, '../node_modules/antd-mobile/lib'), 
  stylesDir: path.join(__dirname, '../src'), 
  varFile: lessPath, 
  publicPath: process.env.MK_ENV ? '/h5' : '',
  srcAlias: 'root', 
  resolvePath: '', 
  async: true,
  pureLess: true,
  generateScopedName: function (name, filename, css) {
    var i = css.indexOf("." + name);
    if (i > -1) {
      let componentName = filename.split(path.sep).slice(-2, -1);
      componentName = utils.camelSplit(componentName[0]);
      return `mk-${componentName}_${name}`;
    } else {
      return name;
    }
  }
};
  • 修改 webpack.common.js,在 webpack 的配置中增加 plugin: @maycur/theme-webpack-plugin(非阳光城环境下)
const themeOptions = require('./themeOptions');
const MKThemePlugin = require('@maycur/theme-webpack-plugin');

if (process.env.THEME_COLOR !== 'yango') {
    plugins.push(new MKThemePlugin(themeOptions));
}
  • 若配置了 css module,需要修改 .gitignore, 忽略 .less.json 文件
/**/*.less.json
  • 在获取到主题色配置之后,使用 less.modifyVars() 更改主题色
window.less.pageLoadFinished.then(() => {
  window.less.modifyVars({
    '@maycur-color': color,
    '@primary-color': color,
    '@brand-primary': color,
    '@brand-primary-tap': color
  }).then(() => {
    // eslint-disable-next-line no-console
    console.log('theme generate successfully');
  });
});
  • 可使用 colorPalette 方法对主题色进行计算,产生一系列色值

Ant Design 色板生成算法演进之路

Ant Design 色彩

@tab-selectd: color(~`colorPalette("@{maycur-color}", 7)`);
  • 调试
"scripts": {
	"debug": "node --inspect --inspect-brk config/start.js"
}

配置说明

属性默认值说明例子
antdStylesDir-UI 组件库样式所在目录path.join(__dirname, '../node_modules/antd-mobile/lib')
businessStylesDir-业务组件库样式所在目录path.join(__dirname, '../node_modules/@maycur/business/lib')
stylesDir-项目样式所在目录,所有该目录下以及嵌套目录下的 .less 文件都会被处理path.join(__dirname, '../src')
indexFileName'index.html'主要的html文件,大多数情况下都是默认值 index.html
varFile主题色相关的变量文件path.join(__dirname, '../src/less/themes/color.less')
publicPath''color.less 会被生成的 dist 目录下,需要配置 publicPath,使 index.html 可以访问到 color.less在 index.html 中,publicPath 会被添加到 /color.less 前面```
// 源代码:
| lessUrl            | '<https://cmimg.maycur.com/dt/static/less.min.js>' | less.js 资源地址                                                                                                                                                                                                                                                                                                                                                         |                                                                                                                                                                                                                                                                                                                                       |
| async              | false                                              | 是否异步加载 less.js                                                                                                                                                                                                                                                                                                                                                       |                                                                                                                                                                                                                                                                                                                                       |
| generateScopedName | -                                                 | 自定义生成的局部 css 类名,在项目配置了 css modules 时需要传入自定义的规则方法                                                                                                                                                                                                                                                                                                                     | ```
generateScopedName: function (name, filename, css) {     var i = css.indexOf("." + name);     if (i > -1) {       let componentName = filename.split(path.sep).slice(-2, -1);       componentName = utils.camelSplit(componentName[0]);       return `mk-${componentName}_${name}`;     } else {       return name;     }   }
``` |
| srcAlias           | '@'                                                | 别名。如在 webpack 中进行了如下配置:```
resolve: {   alias: {     'root': path.resolve(rootPath, 'src'),   }, }
```且在 .less 文件中进行了如下使用:```
@import '~root/less/variables.less'
```时,需要传入。与 resolvePath 配合使用。                                                                                                                                                                      | 'root'                                                                                                                                                                                                                                                                                                                                |
| resolvePath        | 'src/'                                             | 别名对应的路径。```
let str = fs.readFileSync(p).toString(); let reg = new RegExp(`@import +("|')~(${srcAlias})/`, 'g'); // NOTE modify: 将 less 文件中的 ~root 替换为 src 目录 str = str.replace(reg, `@import $1${process.cwd()}/${resolvePath}`);
```可以将@import '~root/less/variables.less'; 替换为@import '/Users/alisa/Documents/Maycur/maycur-dingtalk/src/less/variables.less'; |                                                                                                                                                                                                                                                                                                                                       |
| pureLess           | false                                              | 默认会将项目中以及 UI 组件库中所有与颜色相关的样式提取到 color.less 中。如果觉得生成的 color.less 太大,可以将该值设为 true,就会只提取 varFile 中定义的颜色到 color.less 中。***注:会有 bug,不太推荐使用***                                                                                                                                                                                                                              |                                                                                                                                                                                                                                                                                                                                       |

## 注意事项

varFile 所指定的文件里仅存放需要变更颜色的变量,否则可能会出现以下问题:

-   定义多个相同色值的变量时,调用 modifyVars 不起作用
-   如果 variables.less 里定义了某个色值变量,尽量使用变量,而不要直接写色值,否则可能出现 #FFFFFF 被替换成 @white-colorFFF
-   variables.less 所在目录下的样式文件,互相引入时用相对路径

# 方案2: css variable

**自定义属性**(有时候也被称作**CSS变量**或者**级联变量**)是由CSS作者定义的,它包含的值可以在整个文档中重复使用。由自定义属性标记设定值(比如: **--main-color: black;** ),由 [var()](https://developer.mozilla.org/zh-CN/docs/Web/CSS/var()) 函数来获取值(比如: color: **var(--main-color)** ;)

## 用法:

html { --maycur-color: #0670FF; }

a { color: var(--maycur-color); }

html { --maycur-color: #0670FF; }

@maycur-color: var(--maycur-color);

a { color: @maycur-color; }

document.body.style.setProperty('--maycur-color', '#ff0000');


## 项目落地步骤

-   定义全局 css 变量,并将主题色 less 变量指定为定义的 css 变量

// index.less html { --maycur-color: #0670FF; --maycur-color-1: #EBF3FF; --maycur-color-2: #CCE2FF; }

// color.less @maycur-color: ~'var(--maycur-color)'; @maycur-color-1: ~'var(--maycur-color-1)'; @maycur-color-2: ~'var(--maycur-color-2)';


-   安装 @ant-design/colors,@ctrl/tinycolor,rc-util

<!---->

-   创建 cssVariables.js,用来更改主题色,生成辅助色,并将相关变量插入到 style 标签中

import { updateCSS } from 'rc-util/lib/Dom/dynamicCSS'; import { TinyColor } from '@ctrl/tinycolor'; import { generate } from '@ant-design/colors';

export function registerTheme({ themeColor, varName }) { const variables = {};

// ================ Primary Color ================ if (themeColor) { const primaryColor = new TinyColor(themeColor); const primaryColors = generate(primaryColor.toRgbString());

// Legacy - We should use semantic naming standard
variables[`${varName}`] = themeColor;
primaryColors.forEach((color, index) => {
  variables[`${varName}-${index + 1}`] = color;
});

}

// Convert to css variables const cssList = Object.keys(variables).map( key => --${key}: ${variables[key]};, );

updateCSS( :root { ${cssList.join('\n')} } ); }


-   在获取到主题色配置之后,调用 registerTheme() 更改主题色

import { registerTheme } from '../cssVariables';

registerTheme({ varName: 'maycur-color', themeColor: color });


## 注意事项:

-   color: darken(--maycur-color, 20%); 会报错,可以替换成 --maycur-color-X 使用

# 比较

### less 实时编译

-   代码改动量小

<!---->

-   需要加载额外的资源文件,浏览器端编译对性能小有影响

<!---->

-   项目和第三方 UI 库都需要使用 less 预处理器
-   会将所有与主题色色值相同的变量都改掉,无法做到只修改特定变量的颜色

### css variable

-   代码改动量更小

<!---->

-   实现简单,不需要加载额外资源,[性能影响](https://lisilinhart.info/posts/css-variables-performance/)
-   通用且易操作,对使用的 css 预处理器没有要求

<!---->

-   第三方 UI 库需要支持使用 css 变量切换主题色
-   可以仅更改特定变量的颜色

<!---->

-   [存在浏览器兼容性问题,不支持IE](https://caniuse.com/?search=CSS%20Variables) (css-vars-ponyfill)