what
动态主题是指:当用户主动触发某种交互的时候,页面的颜色内容发生变化;
动态主题最常见的场景就是 黑暗模式
动态主题的实现方式和原理
动态主题的实现方式本质上是和 css 技术栈有关系的:
- less - less.modifyVars
- css - var css
- css in js - js change css
less.modifyVars
这种方式是在页面引入 less.js 以及 less 文件,然后通过修改 less 变量的方式来达到动态换肤的效果
关键代码:
html
<script>
const btn = document.querySelector('button');
btn.addEventListener('click', () => {
less.modifyVars({
'@primary-color': '#cf1322',
});
});
</script>
运行效果:
var css
这种方式是通过在不同的 css 选择器下面定义 css 变量值来达到动态换肤的效果
关键代码:
html
<style>
:root {
--bg: #fff;
--font-color: red;
}
:root[data-dark-theme='true'] {
--bg: blue;
--font-color: black;
}
.container {
width: 100vw;
height: 100vh;
background-color: var(--bg);
color: var(--font-color);
}
</style>
运行效果:
css in js - js change css
css in js 是一种 css 解决方案,它使用 js 将 css 规则插入到页面中
这里以 emotion 为例,我们可以直接修改 theme 对应的主题变量
关键代码:
jsx
const Layout = () => {
const [theme, setTheme] = useState({
colors: {
primary: 'red',
},
});
const btnFn = () => {
setTheme({
colors: {
primary: 'blue',
},
});
};
return (
<ThemeProvider theme={theme}>
<div className={styles['layout-container']}>
<button onClick={btnFn}>change theme</button>
<nav>
{routesList.map((ele) => (
<div
className={styles['btn']}
key={ele}
>
<Link to={ele}>{ele}</Link>
</div>
))}
</nav>
<hr />
<Outlet />
</div>
</ThemeProvider>
);
};
运行效果:
antd@4 动态主题解决方案
antd-theme-generator
antd-theme-generator 是一个将 antd 中所有的 less 变量提取成一个单独文件的插件,可以参见下面的资料:
Ant Design Runtime Theme Update #10007
解决方案的 demo:demo
关键代码:
js
const path = require('path');
const { generateTheme } = require('antd-theme-generator');
const options = {
// 这是 node_modules 中 antd 目录的路径
antDir: path.join(__dirname, './node_modules/antd'),
// 指向包含 .less 文件的自定义样式目录的路径/路径
stylesDir: path.join(__dirname, './src'), // all files with .less extension will be processed
// 主题相关变量文件的路径
varFile: path.join(__dirname, './src/styles/vars.less'), // default path is Ant Design default.less file
// List of variables that you want to dynamically change
themeVariables: [
'@primary-color',
'@link-color',
'@success-color',
'@warning-color',
'@error-color',
// '@font-size-base',
'@heading-color',
'@text-color',
'@text-color-secondary',
'@disabled-color',
'@border-radius-base',
'@border-color-base',
'@box-shadow-base',
],
// 生成的较少内容将写入指定的文件路径,否则不会写入。不过,你可以使用返回的输出,并写入任何你想要的文件中
outputFilePath: path.join(__dirname, './public/color.less'), // if provided, file will be created with generated less/styles
// 该数组用于提供与颜色值相匹配的 regex,大多数情况下您并不需要它
customColorRegexArray: [/^fade\(.*\)$/], // An array of regex codes to match your custom color variable values so that code can identify that it's a valid color. Make sure your regex does not adds false positives.
};
generateTheme(options)
.then((less) => {
console.log('Theme generated successfully');
})
.catch((error) => {
console.log('Error', error);
});
运行效果:
antd ConfigProvider
ConfigProvider 方案的本质是将 primaryColor 这些公共变量抽象成为 css 变量,然后通过调用 ConfigProvider.config 这个 API 去修改这些变量的值(即对应的是 var css 方案)
关键代码:
jsx
const btnCallback = () => {
// 替换主题
ConfigProvider.config({
prefixCls: 'custom',
theme: {
primaryColor: '#002766', // 全局主色
infoColor: '#780650', // info 颜色,Alert 组件的 info 类型的 bg color
successColor: '#092b00', // 成功色
processingColor: '#1890ff', // 这个颜色暂时不知道具有作用在哪些组件里面,在 ConfigProvider 组件中搜索也没有发现使用的记录
warningColor: '#613400', // 警告色
errorColor: '#5c0011', // 错误色
},
});
};
运行效果:
方案比较
方案 | 优点 | 缺点 |
---|---|---|
antd-theme-generator | 1. 支持自定义的范围大,至少支持 12 个 less 变量的自定义 | 1. 有些变量可能不支持,比如 @white 2. 对于不antd@4.17 之前的版本兼容性可能比较好,之后的版本边界需要自己确认一下,可能会存在某些变量不能定义的情况 3. 配置相对而言比较复杂 |
antd ConfigProvider | 1. 配置简单 | 1. 官网文档上说这个是一个实验方案 2. 支持的变量比较少,只支持 6 个变量 |
ConfigProvider Theme 配置的定义:
ts
export interface Theme {
primaryColor?: string;
infoColor?: string;
successColor?: string;
processingColor?: string;
errorColor?: string;
warningColor?: string;
}
方案建议
- 如果自定义主题的场景比较简单,能够使用 ConfigProvider 的 6 个变量实现的话,推荐使用 ConfigProvider 方案
- 如果 ConfigProvider 不能满足自定义的需求,才推荐使用 antd-theme-generator 方案
ant@5 动态主题解决方案
antd@5 是基于 css in js 的,因此技术上直接支持动态主题,加上 antd@5 本身对于动态主题支持也很完成,因此这部分建议直接看官方文档即可 ant.design/docs/react/...
QA
antd 中定义的 less 变量有哪些,如果判断这些变量的影响范围?
根据文档中的描述,antd 中定义的 less 变量有:
less
@primary-color: #1890ff; // 全局主色
@link-color: #1890ff; // 链接色
@success-color: #52c41a; // 成功色
@warning-color: #faad14; // 警告色
@error-color: #f5222d; // 错误色
@font-size-base: 14px; // 主字号
@heading-color: rgba(0, 0, 0, 0.85); // 标题色
@text-color: rgba(0, 0, 0, 0.65); // 主文本色
@text-color-secondary: rgba(0, 0, 0, 0.45); // 次文本色
@disabled-color: rgba(0, 0, 0, 0.25); // 失效色
@border-radius-base: 2px; // 组件/浮层圆角
@border-color-base: #d9d9d9; // 边框色
@box-shadow-base: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 9px 28px 8px rgba(0, 0, 0, 0.05); // 浮层阴影
如果需要判断这些变量的影响范围,我们根据themes/default.less判断