antd动态主题的原理和实现

what

动态主题是指:当用户主动触发某种交互的时候,页面的颜色内容发生变化;

动态主题最常见的场景就是 黑暗模式

动态主题的实现方式和原理

动态主题的实现方式本质上是和 css 技术栈有关系的:

  1. less - less.modifyVars
  2. css - var css
  3. css in js - js change css

less.modifyVars

这种方式是在页面引入 less.js 以及 less 文件,然后通过修改 less 变量的方式来达到动态换肤的效果

demo

关键代码:

html 复制代码
<script>
  const btn = document.querySelector('button');
  btn.addEventListener('click', () => {
    less.modifyVars({
      '@primary-color': '#cf1322',
    });
  });
</script>

运行效果:

var css

这种方式是通过在不同的 css 选择器下面定义 css 变量值来达到动态换肤的效果

demo

关键代码:

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 对应的主题变量

demo

关键代码:

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 变量提取成一个单独文件的插件,可以参见下面的资料:

如何在 umi 系项目中实现动态换肤

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 - 动态主题

ConfigProvider 方案的本质是将 primaryColor 这些公共变量抽象成为 css 变量,然后通过调用 ConfigProvider.config 这个 API 去修改这些变量的值(即对应的是 var css 方案)

方案 demo

关键代码:

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;
}

方案建议

  1. 如果自定义主题的场景比较简单,能够使用 ConfigProvider 的 6 个变量实现的话,推荐使用 ConfigProvider 方案
  2. 如果 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判断

参考链接

  1. 如何在 umi 系项目中实现动态换肤
  2. using-less-in-the-browser
  3. antd default.less
  4. 实现 antd 动态主题的两种方式
  5. antd 定制主题
  6. github antd-theme-generator
  7. emotion - 官方文档
  8. emotion - gitub
  9. antd@5 文档
相关推荐
问道飞鱼6 分钟前
【前端知识】强大的js动画组件anime.js
开发语言·前端·javascript·anime.js
k09338 分钟前
vue中proxy代理配置(测试一)
前端·javascript·vue.js
傻小胖9 分钟前
React 脚手架使用指南
前端·react.js·前端框架
程序员海军22 分钟前
2024 Nuxt3 年度生态总结
前端·nuxt.js
m0_7482567832 分钟前
SpringBoot 依赖之Spring Web
前端·spring boot·spring
web135085886351 小时前
前端node.js
前端·node.js·vim
m0_512744641 小时前
极客大挑战2024-web-wp(详细)
android·前端
若川1 小时前
Taro 源码揭秘:10. Taro 到底是怎样转换成小程序文件的?
前端·javascript·react.js
潜意识起点1 小时前
精通 CSS 阴影效果:从基础到高级应用
前端·css
奋斗吧程序媛1 小时前
删除VSCode上 origin/分支名,但GitLab上实际上不存在的分支
前端·vscode