likes
comments
collection
share

antd 系动态切换主题实践,含 IE 兼容方案

作者站长头像
站长
· 阅读数 69

简介

动态切换主题通常指的更多的是运行时切换页面主题的功能,比如用户通过点击页面中的一个按钮来达到几种预制主题的切换,这一般都是通过提前将几种预制主题的样式进行独立编译,供用户选择使用。这只是动态主题的基本功能,更进一步的,应该允许用户定制和设计页面中的任何一个组件元素。

动态切换主题的需求一般在“多租户”的场景会是一个比较强烈的需求,可以实现同一套代码,在不同的租户现场又能呈现不同的页面风格的好处。但是在技术实现上却存在不少难点,这个功能常常因为体验太差,或者影响页面性能而遭到用户的吐槽。

很多人第一次了解到这个功能,应该是从 ant-design-pro 项目的主题设置功能开始的。

antd 系动态切换主题实践,含 IE 兼容方案

但是当你想把这个功能用到项目中时,会得到一个提示:“配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改配置文件”。

本文整理了如今 antd 系列组件的动态切换主题实践,其中不同的方案存在不同的优点和弊端,请在选择方案时,酌情考虑。

实践

antd-mobile@2.x 使用的技术和 antd@4 基本一致,所以以下以 antd@4 举例的方案,也能用于 antd-mobile@2.x

不支持 IE 是本文中很多方案的共同缺点

antd@5

antd@5 采用的是 cssinjs 的技术实现,所以在实现动态切换主题能力的时候,可以说是最轻松的,也可以说“动态主题”也是推动 antd 到 5 一个很重要的原因。

在 v5 中,动态切换主题对用户来说是非常简单的,可以在任何时候通过 ConfigProvider 的 theme 属性来动态切换主题,而不需要任何额外配置。

import { Button, ConfigProvider } from 'antd';
import React from 'react';

const App: React.FC = () => (
  <ConfigProvider
    theme={{
      token: {
        colorPrimary: '#1677ff',
      },
    }}
  >
    <Button />
  </ConfigProvider>
);

export default App;

直接从服务端或者用户修改 antd token 传递给 theme,就能实现动态配置主题了。技术实现上非常的简便,但是很多现有项目多是基于 antd@4 版本构建的,要直接切换到 antd@5 成本有点大,而且需要注意的是 antd@5.0 之后不再支持 IE。

antd@4.17.0-alpha.0 CSS 变量

antd@4 从 4.17.0-alpha.0 通过 CSS 变量支持了动态切换主题的功能,由于 IE 不支持 CSS 变量,因此在 IE 浏览器环境中也无法使用这个方案。

修改全局的 CSS 变量对所有的组件生效

:root:root {
  --ant-primary-color-hover: #c89deb;
}

也可以指定部分的组件生效,如在 <div className="purple-theme"> 包裹下的组件生效。

.purple-theme {
  --ant-primary-color-hover: #c89deb;
}

优点显而易见,通过动态的注入 CSS 变量,就能达到主题切换的功能,但是需要修改和注意的地方不少。如果有成体系的资产方案,改动较大。

如果你是使用全局的 CSS 文件,那需要换成带有 CSS 变量的全局文件。

-- import 'antd/dist/antd.min.css';
++ import 'antd/dist/antd.variable.min.css';

如果基于 antd@4 封装的组件,需要将 less 变量引用换成带有 CSS 变量的文件

-- @import (reference) '~antd/lib/style/themes/default.less';

++ @import (reference) '~antd/lib/style/themes/variable.less';

而且需要注意的是不能在任意地方再次重新定义 antd 支持的原有的 less 变量,因为会导致最终编译产物中的 less 变量没有使用 CSS 变量,在项目中的修改,应该通过修改对应 CSS 变量的方式实现自定义。

如果修改了前缀 prefixCls,如:

import { ConfigProvider } from 'antd';

export default () => (
  <ConfigProvider prefixCls="custom">
    <MyApp />
  </ConfigProvider>
);

需要重新编译一份 css

lessc --js --modify-var="ant-prefix=custom" antd/dist/antd.variable.less output.css

antd-mobile@5 CSS 变量

antd-mobile@5 本身就大量使用了 CSS 变量,现有的移动端设备低版本浏览器的场景也比较少,影响并不大。但是相比于 antd-mobile@2 的项目还是比 antd-mobile@5 要多的。

同样的也支持全局覆盖和局部覆盖的方式

:root:root {
  --adm-color-primary: #a062d4;
  --adm-button-border-radius:200px;
}
.purple-theme {
  --adm-color-primary: #a062d4;
  --adm-button-border-radius:200px;
}

与 antd@4 不同的是,antd-mobile@5 还支持直接通过 style 传递 CSS 变量

<Button style={{
  '--border-radius': '2px'
}}/>

antd@4 动态 less 编译

先说结论吧,需要将所有用到的 less 编译成一个较大的 less 文件挂载到 html 中,然后在运行时,使用 less.modifyVars 方法动态编译 less。是优点也是缺点,在 IE 浏览器上 less 编译需要 5-6s 。

通过 webpack 插件来实现以上功能,通过 antd-pro-merge-less 将用到的 less 文件合并为一个文件。

import MergeLessPlugin from 'antd-pro-merge-less';
const outFile = path.join(__dirname, '../node_modules/.temp/merge.less');
const stylesDir = path.join(__dirname, '../src/');

    config.plugin('merge-less').use(new MergeLessPlugin(), [
      {
        stylesDir,
        outFile,
      },
    ]);

然后通过 antd-theme-webpack-plugin 插件将合并后的 less 文件和 antd 的 less 变量生成特定颜色变量的 less 文件,并注入到 html 中。

import AntDesignThemePlugin from 'antd-theme-webpack-plugin';

const outFile = path.join(__dirname, '../node_modules/.temp/merge.less');
const stylesDir = path.join(__dirname, '../src/');

    config.plugin('ant-design-theme').use(AntDesignThemePlugin, [
      {
        antDir: path.join(__dirname, '../node_modules/antd'),
        stylesDir,
        varFile: path.join(
          __dirname,
          '../node_modules/antd/lib/style/themes/default.less',
        ),
        mainLessFile: outFile,
        // themeVariables: ['@primary-color',],
        indexFileName: 'index.html',
        generateOne: true,
        lessUrl: 'https://gw.alipayobjects.com/os/lib/less.js/3.8.1/less.min.js',
      },
    ]);

执行构建后会生成一个 color.less 文件,当你执行动态编译时,会挂在 <link rel="stylesheet/less" href="/color.less"> 并且生成一个 <style type="text/css" id="less:color"></style> 覆盖你的现有样式。

这两个插件需要锁定版本,因为不同版本功能不同 "antd-pro-merge-less": "1.0.0", "antd-theme-webpack-plugin": "1.3.9",

antd@4 粗暴的 CSS 样式覆盖

由于 antd@4 和 antd-mobile@2 已经进入稳定维护期,因此它的 css 结构不会发生变化,我们就可以通过简单粗暴的 CSS 样式覆盖的方式来实现动态主题切换。

通过在浏览器端编译 css ,采用 css 注入的方式,来进行动态覆盖。同样的可以指定 id 或者 class 选择器下生效的方式来实现局部的样式覆盖。

import { Button } from 'antd';
import { normalizeCSS, insertRules } from 'theme-utils';

export default () => {
  return (
    <div id="purple-theme">
      <Button
        type="primary"
        block
        size="large"
        onClick={() => {
          const css = `.ant-btn-primary {
            border-color: red;
            background: red;
          }
          `;
          const a = normalizeCSS(css, '#purple-theme');
          insertRules('12312', a);
        }}
      >
        注入(模拟预览,只有部分生效)
      </Button>
    </div>
  );
};

这个方案最大的好处就是兼容性好,复杂的地方维护很繁琐,因此我将这个方法整理到了 theme-utils 包中。

除了上文中提到的浏览器端编译 css 的功能外,还支持根据指定模版,将对象的值解析道模版中,生成最终的 css 字符串(stringifyCss)。这样只需要简单的维护各个组件的 css 模版即可。

下文通过几个场景的简述,来说明如何借助 theme-utils 的能力在页面配置主题。

新建场景描述:

1、通过编辑页面 form 得到,theme 的对象 如 { backgroundColor:'red',fontSize:'12px'}

2、根据模版 '.cc{ background-color: backgroundColor; font-size: fontSize;}' 使用 stringifyCss 解析 css

3、得到最终 css '.cc{ background-color:red; font-size:12px;bor:123}'

4、将最终 css 提交到服务端

编辑场景描述:

1、从服务端取得 css

2、使用 parseCss 根据模版和 css 字符串解析出配置对象 { backgroundColor:'red',fontSize:'12px'}

3、将对象赋值到编辑页面 form

4、重复新建场景

预览场景描述:

1、每次编辑都会实时的生成 css

2、通过 normalizeCSS(css,'#previewId') 将 css 作用到指定 id dom 下 ' #previewId { .cc{ background-color:red; font-size:12px; }}'

3、通过 insertRules('12312', css, document.getElementById('previewId')); 将 style 挂载到预览 dom 上

4、挂载的样式仅会对预览生效

5、这个能力也可用于自定义生效,在动态挂在样式的时候,可以做到局部覆盖的能力。

使用场景描述:

1、通过 insertLink('12312', 'xxx.css',true); 在页面上加载所有的生成的 css,将会对所有的场景生效。

总结

上文中主要提到了 antd 系动态切换主题实践,主要涉及的技术是 cssinjs、CSS 变量、less 动态编译和 CSS 样式覆盖。从上文中我们能够看到如果应用场景需要兼容 IE 那最好的选择就是 CSS 样式覆盖。

技术优点弊端IE 兼容性能
cssinjs实现简单,可自定义内容最多新方案,推进成本较高,兼容性较差不兼容
CSS 变量实现简单,对部分变量替换较为快速过于依赖浏览器的能力不兼容
less 动态编译浏览器兼容较好,变更较齐全性能消耗较高,实现较难,需要维护构建插件兼容
CSS 样式覆盖兼容性最好,使用场景较多只能用于稳定维护期的资产方案中,要求组件层级结构不能发生变化兼容

演示demo

theme-demo

转载自:https://juejin.cn/post/7262722681153536055
评论
请登录