实现antd动态主题的两种方式

7,531 阅读4分钟

前言

本文环境基于umi框架,不过实现的原理在不同框架中是通用的。

方案一:利用less.modifyVars

原理简述

通过antd-theme-generator抽取项目中所有涉及到颜色变量的样式,整合到一个less文件,然后再html中引入这个样式文件和less来对原样式进行覆盖,再利用less在浏览器中可以修改变量的特性来实现主题色的切换。

这个方案适用于antd@4.17之前的版本,之后的版本会报错

准备工作

1.安装依赖

yarn add antd-theme-generator -D

2.引入less库

可以通过script引入,也可以下载下来放到本地目录中。cdn链接:cdn.bootcss.com/less.js/2.5…

通过脚本抽离样式

1.创建样式变量文件var.less

虽然umi配置可以直接设置主题色,但是由于antd-theme-generator会覆盖umi打包的样式,我们需要针对antd-theme-generator抽离的样式设置主题色。

// 根目录/src/var.less
@import '~antd/lib/style/themes/default.less';

@primary-color: #76488c;

如果你是用默认主题色可以不做这一步

2.创建脚本color.js

// 根目录/script/color.js
const { generateTheme } = require('antd-theme-generator');
const path = require('path');

const options = {
    // antd的路径
    antDir: path.join(__dirname, '../node_modules/antd'),
    // 处理颜色相关less样式的目录
    stylesDir: path.join(__dirname, '../src'),
    // 样式变量文件
    varFile: path.join(__dirname, '../src/var.less'),
    // 需要抽离的颜色变量,默认是@primary-color,可自行添加其他的变量
    themeVariables: ['@primary-color'],
    // 输出路径,这里输出到public,命名为color.less,也可以自定义命名,但需要
    // 和下文引入link的名字对应。
    // 在umi中public的文件会复制到打包目录。
    outputFilePath: path.join(__dirname, '../public/color.less'),
};

// 生成样式
generateTheme(options)
  .then((less) => {
    console.log('Theme generated successfully');
  })
  .catch((error) => {
    console.log('Error', error);
  });

3.在package.json中添加脚本命令

"script": {
    "color": "node ./script/color.js"
}

然后我们可以在控制台用过yarn color执行脚本,将样式文件抽离并生成到public目录下。

项目内配置

在项目入口文件执行

// app.ts
const setColor = () => {
  const publicPath = '' // 项目的publicPath,没有配置的可以置空
  const lessConfigNode = document.createElement('script');
  const lessScriptNode = document.createElement('script');
  const lessStyleNode = document.createElement('link');
  lessStyleNode.setAttribute('rel', 'stylesheet/less');
  lessStyleNode.setAttribute('href', publicPath + '/color.less');

  lessConfigNode.innerHTML = `
      less = {
        async: true,
        env: 'development',
      };
    `;
  lessScriptNode.src = 'https://cdn.bootcss.com/less.js/2.5.3/less.min.js';
  lessScriptNode.async = true;
  // 引入顺序不能改变
  document.body.appendChild(lessStyleNode);
  document.body.appendChild(lessConfigNode);
  document.body.appendChild(lessScriptNode);
};

export function render(oldRender) {
  setColor()
   
  oldRender()
}

至此,我们便可以在项目中动态修改主题,例如:

// 在某个组件内
<input type="color" onChange={(e)=>{
    // less已经在script中引入作为全局变量
    less.modifyVars({
    /: e.target.value,
    })
}} />

方案二:利用css variable

原理简述

css variables提供了利用js修改css变量的方式,antd@4.17版本开始官方提供了利用css variables配置主题的方式。

这个方案只适用于antd@4.17及之后的版本

引入antd.variable.min.css

// global.less
@import '~antd/dist/antd.variable.min.css'

引入这个样式文件之后不需要再引入antd/dist/antd.min.css

通过ConfigProvider配置样式变量

import { ConfigProvider } from 'antd'; 
ConfigProvider.config({
    theme: { primaryColor: '#25b864', },
});

至此我们就可以成功修改主题色了。

如何在非antd组件使用antd提供的css变量?

我们可以查看antd/lib/style/themes/variable.less,这里定义了主题颜色相关的css变量,我们可以直接在其他自己写的组件中使用这些变量,例如:

// 组件
<div className='color'></div>
.color{
    background: var(--ant-primary-color);
}

通过ConfigProvider修改主题色的时候也会一并改变。

不过,对于一些老项目中可能会有不少地方用了例如@primary-color这些less变量,这时候我们肯定希望可以无痛切换到css变量的,这时候我们可以利用less-loader来实现:

// .umirc
...
    lessLoader: {
        // 通过globalVars在每个less文件头引入antd定义的variable.less文件,里面有less变量和css变量
        // 相关的映射
        globalVars: {
            theme: 'true;@import "~antd/lib/style/themes/variable.less"',
        }
    }
...

注意:做了这个配置之后不要再配置umi的theme,否则会不生效,通过ConfigProvider去配置初始的主题色

对于非umi框架也可以通过自行调用less-loader去实现,不过要注意less-loader不同版本的用法,现在最新版的好像已经没有globarVars,改为additionaldatal了,本人也还没有测试过,各位自行测试。

开启dynamicImport导致antd默认样式覆盖的问题处理

这种情况我们可以通过splitchunk抽离antd样式和普通样式,让antd样式先于普通样式引入:

// .umirc
...
  chainWebpack(config) {
    config.optimization.splitChunks({
      cacheGroups: {
        common: {
          name: 'common',
          test: /\.(css|less)$/,
          chunks: 'async',
          minChunks: 1,
          minSize: 0,
          priority: 10,
          reuseExistingChunk: true,
        },
        antdVendor: {
          test: /[\\/]node_modules[\\/](antd)[\\/]/,
          name: 'antdVendor',
          priority: 20,
        },
      },
    });
  },
  chunks: ['antdVendor', 'common', 'umi'],
...

总结

对比两种方案:

优点缺点
方案一适用于范围较广,可用于4.17之前的版本配置较复杂,antd-theme-generator较长时间没有更新,对于较新的版本不再支持
方案二配置简单,使用方便不支持旧版本,目前来说也是实验性方案,不知未来会怎样变化

总的来说,个人推荐是尽量用方案二,实在不行再用方案一。

最后,如果觉得文章有帮助的话,别忘了点个赞哦!

参考

less代码打包后组件样式错误覆盖