Antd Dynamic Theme
方案要点
- 使用 antd-theme-generator 插件
- 放弃
css-module
(该插件不支持), 改用 CSS-BEM
步骤
1.安装依赖
yarn add @craco/craco craco-antd antd-theme-generator@1.2.6 -D
yarn add less antd@^3
由于项目使用的是 antd@3
, 需要安装指定的版本 antd-theme-generator@1.2.6
, 如果安装最新版会有报错.
如果使用 antd@4
, 则直接安装最新版本的 antd-theme-generator
即可.
2. 创建变量定义文件
src/styles/variables.less
这个文件包含了所有的 antd 定义变量(函数)以及项目内自定义的变量
// 需要先引入 antd 的变量
@import '~antd/lib/style/themes/default.less';
// 配置 antd 主题变量的默认值
@primary-color: rgb(42, 187, 103);
// 项目内部的变量(不要与antd的变量冲突)
@theme-color: rgb(42, 187, 103);
3.创建脚本文件
scripts/color.js
这个文件用来生成在生产模式下可运行的 less 样式文件
// antd 主题色文件生成脚本
const fs = require('fs')
const path = require('path')
const { generateTheme } = require('antd-theme-generator')
const themeVariables = ['@primary-color', '@theme-color']
// 由于 antd@3.x 的库不一样, 需要使用 1.2.6 版本
const options = {
// antd 库的路径
antDir: path.join(__dirname, '../node_modules/antd'),
// 需要检索的所有 less 文件的根目录
stylesDir: path.join(__dirname, '../src'),
// 自定义变量的文件
varFile: path.join(__dirname, '../src/styles/variables.less'),
// 哪些变量值是需要动态修改的
themeVariables,
}
// 由于插件提取的样式内容有很多是冗余的, 不使用插件默认的导出功能
// 需要先处理数据, 然后再自己导出文件
generateTheme(options)
.then((less) => {
console.log("less 文件内容提取成功");
// 生成的 less 存放的位置
const outputFilePath = path.join(__dirname, "../public/color.less");
let content = less.replace(/([,{])\n/g, "$1");
const arr = content
.split("\n")
.map((str) => str.trim())
.filter((str) => {
// 纯类样式或变量定义
const isClassStyleOrVars =
/^\.((?!\(\)).)*\{.*?\}$/gm.test(str) || /^@.*?:.*?;$/gm.test(str);
// 字符串中不包含任意的主题变量
const excludeThemeVars = themeVariables
.map((k) => k.slice(1))
.every((k) => !str.includes(k));
// 其实还有其他的冗余内容, 但是影响不大
return !(isClassStyleOrVars && excludeThemeVars);
});
content = arr.join("\n");
fs.writeFileSync(outputFilePath, content);
console.log("主题样式文件编译成功");
})
.catch((error) => {
console.log("Error", error);
});
4.打包配置
目的: 让框架支持 less ; 并引入 antd 的样式文件(less)
由于项目是基于 create-react-app
生成的, 并且不想使用 yarn eject
,所以使用了 craco
进行自定义配置.
当然也可以使用其他的方式, 只要保证能正常使用 antd 组件即可.
新建 craco.config.js
const CracoAntDesignPlugin = require("craco-antd");
module.exports = {
plugins: [
// antd 按需加载(不使用这个插件, 使用全局引入 antd 样式也可以)
{
plugin: CracoAntDesignPlugin,
options: {
babelPluginImportOptions: {
libraryName: "antd",
libraryDirectory: "es",
style: true,
},
},
},
],
};
修改 package.json
{
"scripts": {
"dev": "node ./scripts/color.js && craco start",
"build": "node ./scripts/color.js && craco build",
"color": "node ./scripts/color.js",
},
}
先运行项目, 检测是否配置成功
yarn dev
5.修改 index.html 入口文件
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
</head>
<body>
<!-- antd theme less -->
<link rel="stylesheet" type="text/less" href="%PUBLIC_URL%/color.less" />
<script>
// https://less.bootcss.com/usage/#browser-usage-setting-options
window.less = { javascriptEnabled: true, logLevel: 3 }
</script>
<!-- antd theme less end -->
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
注意 引入的 less 标签需要写在
body
标签下, 否则会被动态引入的样式文件覆盖(CSS 权重的问题)
6.在项目入口文件引入 less
scr/index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
// 引入 less, 初始化主题配置
import 'less'
import './index.css';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
7.运行查看效果
yarn color
yarn dev
运行 yarn color 之后, 会生成 public/color.less
文件, 然后在运行的项目中, 初始化时, 会自动根据这个 less 文件生成一个对应的 css 样式标签
8.动态修改主题色
使用 less.modifyVars()
即可修改 less 变量. 这里提供了一个组件供参考.
import React, { useCallback } from 'react'
import { Button, DatePicker, Pagination } from 'antd'
import less from 'less'
const colors = ['#a12356', '#0215a6', '#f120a1']
const ThemeSetter = () => {
const onColorChange = useCallback((color) => {
less.modifyVars({
'@primary-color': color,
'@theme-color': color,
})
}, [])
return (
<>
<h1>color setter</h1>
{colors.map((c) => (
<button key={c} onClick={() => onColorChange(c)}>
{c}
</button>
))}
<hr />
<Button>A</Button>
<Button type='primary'>A</Button>
<DatePicker />
<Pagination total={100} />
</>
)
}
export default ThemeSetter
总结
接下来梳理一下原理. 首先, 确保项目可以编译 less 样式文件, 并正常引入了 antd.
核心文件 scripts/color.js
; public/color.less
- 根据配置项
varFile: path.join(__dirname, '../src/styles/variables.less')
找到所有需要跟踪的 less 变量 - 从
antDir: path.join(__dirname, '../node_modules/antd')
配置项中指定 antd 库文件中遍历所有的 less 文件, 提取所有包含需跟踪变量
(主要是antd的) 的代码 - 从
stylesDir: path.join(__dirname, '../src')
配置项中指定的路径遍历所有的 less 文件, 提取出所有包含需跟踪变量
(主要是自定义的) 的代码 - 生成
public/color.less
文件 public/index.html
引用了public/color.less
; 并在页面初始化时, 通过less
编译出对应的css
样式.- 在页面中, antd 组件或使用了主题变量的组件先通过组件中引入的 less 文件已经编译的 css 样式首次渲染, 然后被
public/color.less
编译出来的样式覆盖, 重新渲染. 达到修改颜色样式的效果 - 在此期间, 通过
less.modifyVars()
再次编译出新的样式, 再次覆盖样式后渲染. 以此实现动态修改主题色的功能
需要注意的是, 每次修改或新增一个自定义变量或样式文件, 都需要执行
yarn color
重新生成主题文件. 但是在开发环境中一般需要这么做, 只需要在打包前执行一次即可.