公司的项目是基于ant design开发的,年前初步完成了整个系统的动态主题切换,主要是利用了antd-theme-generator这个插件,使用 less 的 modifyVars 来更改主题。

这里要先交待一下我的依赖版本,ant design是3.19.1,antd-theme-generator是1.2.6,如果你的项目用的是ant design 4.x版本,可以直接安装最新的antd-theme-generator,如果跟我一样是接手的ant design 3.x的项目,就要和我一样用1.2.6版本的antd-theme-generator,否则后面开发的时候在生成主题less文件的时候会报错:LessError: error evaluating function darken: color.toHSL is not a function。导致无法生成主题less文件。

安装了正确版本的antd-theme-generator插件后,首先在 scripts 文件夹下添加脚本 scripts/generateColorLess.js,这个脚本执行后会生成能实现动态主题切换的less文件:

const path = require('path');
const { generateTheme } = require('antd-theme-generator');

const options = {
    stylesDir: path.join(__dirname, "../src/styles"),
    antDir: path.join(__dirname, "../node_modules/antd"),
    varFile: path.join(__dirname, "../src/styles/variables.less"),
    mainLessFile: path.join(__dirname, "../src/styles/index.less"),
    themeVariables: [
        "@primary-color",
        "@secondary-color",
        "@text-color",
        "@text-color-secondary",
        "@heading-color",
        "@layout-body-background",
        "@layout-header-background",
        "@border-radius-base"
    ],
    outputFilePath: path.join(__dirname, "../public/color.less")
};

generateTheme(options)
    .then(less => {
        console.log("Theme generated successfully");
    })
    .catch(error => {
        console.log("Error", error);
    });

上面的代码中主要需要了解一下options中的各个配置项:

  1. stylesDir指定了我们用来定制主题的less文件的路径;
  2. antDir指定了ant design依赖的路径;
  3. varFile指定了要动态切换的less变量所在的文件的路径;
  4. mainLessFile指定了你编写的样式文件,也就是index.less文件的路径,要在这个文件中编写我们需要动态切换的样式,并将variables.less中的less变量应用到我们编写的样式中;
  5. themeVariables数组指定所有我们自定义的需要切换的样式变量。

接着,在 src 下创建文件 src/styles/variables.less 和 src/styles/index.less。这两个文件用于定义全局的主题样式,在variables.less文件中给我们要动态切换的less变量赋初始值:

// variables.less
@import "~antd/lib/style/themes/default.less";
@primary-color: #1890ff;

注意上面的代码中引入了antd默认主题less文件,并给@primary-color变量赋值,@primary-color"~antd/lib/style/themes/default.less"中定义的变量,在variables.less中可以对ant design默认的组件或者主题样式变量进行覆盖。variables.less中赋了初始值的less变量要和generateColorLess.js中的themeVariables数组中的less变量一一对应。

在index.less中编写我们的自定义样式:

// index.less
.base-color {
    color: @primary-color;
}

在需要应用@primary-color颜色的HTML元素上添加base-color类名,即可动态改变该HTML元素的颜色。

接下来修改 public/index.html,主要修改的部分如下:

<body>
  <link rel="stylesheet/less" type="text/css" href="%PUBLIC_URL%/color.less" />
    <script>
      window.less = {
        async: false,
        env: 'development',
        javascriptEnabled: true
      };
    </script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/less.js/2.7.2/less.min.js"></script>

color.less是通过执行generateColorLess.js生成的主题样式文件,上述代码中引入了color.less,对less做了基本配置,并引入了less.js。

然后在package.json中增加执行generateColorLess.js的脚本:

"scripts": {
    "start": "node ./scripts/generateColorLess.js && react-app-rewired start",
    "build": "node ./scripts/generateColorLess.js && react-app-rewired build",

以上就是动态主题切换的一些基本配置,定义一个主题切换的方法,调用这个方法即可实现主题切换:

setThemeColor(theme) {
    if (theme === 'default') {
        window.less.modifyVars({
            '@border-color-base': '#E9EEF7',
            '@layout-body-background': '#f0f2f5',
            '@layout-header-background': '#fffffe',
            '@component-background': '#fffffe',
            '@text-color': 'fade(@black, 65%)',
            '@title-color': '#333',
            '@table-header-bg': 'hsv(0, 0, 98%)',
            '@ant-selected-color': 'rgba(0, 0, 0, 0.65)',
            '@table-row-hover-bg': '#F0FDFF',
            '@modal-heading-color': 'fade(#000, 85%)',
            '@page-ellipsis-color': 'rgba(0, 0, 0, 0.25)',
            '@empty-text-color': 'rgba(0, 0, 0, 0.25)',
            '@date-last-next': 'rgba(0, 0, 0, 0.25)',
            '@cursor-text-color': '#1f1f1f',
            '@descriptions-title-color': 'rgba(0, 0, 0, 0.85)',
            '@descriptions-label-bg-color': '#fafafa',
            '@alarm-chart-bg-color': '#f2f6fa',
            '@statistic-title-color': 'rgba(0, 0, 0, 0.45)',
            '@dashboard-box-shadow': '0 1px 4px rgba(0, 0, 0, 0.15)'
        }).catch(error => {
            message.error(`Failed to update theme`);
        });
    } else {
        window.less.modifyVars({
            '@border-color-base': '#303030',
            '@layout-body-background': '#000001',
            '@layout-header-background': '#1f1f1f',
            '@component-background': '#141414',
            '@text-color': 'rgba(255, 255, 255, 0.65)',
            '@title-color': 'rgba(255, 255, 255, 0.85)',
            '@table-header-bg': '#1d1d1d',
            '@ant-selected-color': '#1f1f1f',
            '@table-row-hover-bg': '#000001',
            '@modal-heading-color': 'fade(@white, 100%)',
            '@page-ellipsis-color': 'rgba(255, 255, 255, 0.25)',
            '@empty-text-color': 'rgba(255, 255, 255, 0.45)',
            '@date-last-next': 'rgba(255, 255, 255, 0.25)',
            '@cursor-text-color': '#fefefe',
            '@descriptions-title-color': 'rgba(255, 255, 255, 0.85)',
            '@descriptions-label-bg-color': '#101010',
            '@alarm-chart-bg-color': '#0d0905',
            '@statistic-title-color': 'rgba(255, 255, 255, 0.45)',
            '@dashboard-box-shadow': '0 1px 4px rgba(255, 255, 255, 0.4)'
        }).catch(error => {
            message.error(`Failed to update theme`);
        });
    }
}

这里以我项目中用到的默认主题和暗黑主题为例,上述代码中提供了两套主题配色。

最后有两个需要注意的点:

  1. less变量在variables.less和generatorColorLess.js的themeVariables数组和modifyVars里都要写,否则无法实现主题切换;
  2. 部分无法正常切换的变量要去antd依赖的default.less里看这个变量是不是直接用了别的变量,如果是的,要切换这个变量使用的那个变量。

参考资料

  1. mzohaibqc/antd-theme-generator
  2. ant design 动态切换主题
  3. LessError: error evaluating function darken: color.toHSL is not a function