阿里数据的umi + antd动态主题实践

随着嵌入阿里数据页面的需求增多,出现了一些要求较高的业务方,希望能指定阿里数据的主题色,来和他们的品牌色保持一致,让产品有更好的体验。于是我们开始了动态主题功能的调研,本来以为是个比较简单的事情,实际还是有遇到一些曲折的,不过最终效果挺满意的,符合我们的需求,同时很简单、很轻量。

一、现有方案

动手之前先调研了一波可选的方案,现有方案大致是两类:一类是编译构建的时候就有多份预设主题,在运行时只做样式切换,新增一类主题需重新构建发布;另一类是运行时可指定任意主题,新增一类主题几乎没有成本,很方便拓展。

1、编译多份预设主题

Class切换

编译出的CSS示例如下,通过给body设置theme-lighttheme-darkclass来切换主题。借助Less/Sass的能力可以较方便批量处理,但弊端也很明显,CSS文件大小会倍增。

// index.css
.theme-light div {
    color: #000;
}

.theme-dark div {
    color: #fff;
}

构建多份CSS

对于class切换方案的弊端,我们可以通过构建多份css文件来解决,比如我们的less代码如下:

div {
    color: @primary-color;
}

然后构建阶段指定@primary-color为不同色值,来得到多份css产物:

// index-light.css
.div {
    color: #000;
}
// index-dark.css
div {
    color: #fff;
}

在运行期间,通过切换加载index-light.cssindex-dark.css,来达到切换主题的效果。但这个方案也不友好,需要侵入构建配置,然后构建耗时会增加。

在编译阶段预置多份主题的方案,实现还算简单,原理也很好理解,但都有存在明显的弊端,且拓展新主题会有成本。

2、运行时指定任意主题

如果要低成本拓展主题,就必须借助运行时的能力了,实现复杂度由难到易,大致有下面三种方案:

CSS模板+运行时替换

这类方案需要先准备一个css模板文件,然后根据用户指定的颜色值注入到模板里去,动态产出最终的css文件,Element-ui便是使用了这个方案,在切换颜色时,会把色值作为参数传给后端来得到新的css文件:

Element-ui动态主题.png

当然这个工作纯前端也可以做,方案还是挺不错的。

Less变量

这个方案借助less.js的运行时能力,来实现类似上面方案的效果,css模板在这里成了less文件,运行时替换由less.jsmodifyVars提供,我们的编码工作量变少了。使用大概是这样:

  • html主文档增加项目的less文件和less.js
<link rel="stylesheet/less" type="text/css" href="/styles.less" />
<script src="https://cdn.jsdelivr.net/npm/less@4.1.1"></script>
  • 使用modifyVars修改颜色变量
window.less.modifyVars({
  '@primary-color': '#0035ff'
})

该方案的弊端不少,一是要引入40kb的less.js运行时;二是对编码和打包一定侵入,我们需要把所有less文件加到html中;三是主题切换不会很流畅,因为modifyVars后涉及到less文件的重新编译。

CSS变量

CSS变量是CSS3标准的新功能,通过它做主题很简单,比如下面是我们的样式:

div {
    // 使用--primary-color变量的颜色,无值则用默认的#000
    color: var(--primary-color, #000); 
}

切换主题时,只需通过document的API设置新的颜色值即可

document.body.style.setProperty('--primary-color', '#fff')

感觉这个方案是上述所有方案里最简单的,功能强大也很好理解。

此外也有类似类似styled-components的css in js方案,但对项目改动太大了,且指定antd组件的主题会很麻烦,可以直接忽略。

二、我们的选择

编译多份预设主题类型的方案首先被我们拍死了,我们对低成本拓展的要求比较高,否则业务方如果换主题色,我们还得跟着发版...

运行时指定任意主题的方案里,功能上都能满足我们的需求,其中CSS变量方案成本最低,且:

  • 主流浏览器都已支持,然后我们产品的用户99%+都是chrome,兼容性可以不考虑;
  • 然后我们本身已经使用了less,通过把@primary: #ff6a00改成@primary: var(--primary-color, #ff6a00)可以很方便使用,不需要大量改动项目中已有的less文件;
  • 我们发现antd也已经支持了CSS变量动态指定主题

完美,所以我们最终选择了CSS变量方案。

三、动手实践

1、修改global.less

把写死的less变量的值,改成CSS变量的方式,原先的值放入默认值

- @primary: #ff6a00;
- @primary5: #ff6a000d;
- @primary15: #ff6a0026;
- @primary75: #ff6a00BF;

+ @primary: var(--primary-color, #ff6a00);
+ @primary5: var(--primary-color-5, #ff6a000d);
+ @primary15: var(--primary-color-15, #ff6a0026);
+ @primary75: var(--primary-color-75, #ff6a00BF);

本地跑一下,没啥问题,颜色都正常

2、修改CSS变量

我们新增了一个theme.ts文件,在入口会执行它的setupTheme来使用URL参数上指定的主题色。

// theme.ts
import { getUrlParams } from '@/utils/utils';

export let primaryColor = '#ff6a00';
export let primaryColor5 = '#ff6a000d';
export let primaryColor15 = '#ff6a0026';
export let primaryColor75 = '#ff6a00BF';

export function setupTheme() {
  const params = getUrlParams() as any;
  if (params?.primaryColor) {
    primaryColor = `#${params?.primaryColor.toLocaleLowerCase()}`;
    primaryColor5 = `${primaryColor}0d`;
    primaryColor15 = `${primaryColor}25`;
    primaryColor75 = `${primaryColor}BF`;
  }
  document.body.style?.setProperty('--primary-color', primaryColor);
  document.body.style?.setProperty('--primary-color-5', primaryColor5);
  document.body.style?.setProperty('--primary-color-15', primaryColor15);
  document.body.style?.setProperty('--primary-color-75', primaryColor75);
}

本地跑一下,指定primaryColor参数为其它色值,除了antd系列组件都已经生效了

3、指定antd组件主题

跟着文档指引,我们升级了antd4.17.1-alpha.1版本,importantd/dist/antd.variable.min.css,然后在theme.ts里新增了ConfigProvider来设置主题色,刷新下页面,antd系列组件也生效了,完美!打包发到预发感受下...

发到预发后问题来了,antd只有部分组件主题生效了,有些组件如分页器没生效,调试发现antd样式有冗余,部分组件的CSS变量版样式被覆盖了,我们的umi.css大概是这样:

// CSS变量版的样式在前,被后面的覆盖了,导致指定的主题色没生效
.ant-pagination-item-active {
        border-color: var(--ant-primary-color);
}

.ant-pagination-item-active {
        border-color: #ff6a00;
}

我的第一反应是修改下CSS顺序,让CSS变量版的样式在后,折腾了一番发现不行,发现webpack打包后的CSS顺序不和import顺序一致,最后看到这个issue就决定放弃了,webpack成员表示他们不能保证这个顺序。

4、关闭antd的按需加载

antd的文档所写,antd动态主题需要关闭按需加载:

注:如果你使用了 babel-plugin-import,需要将其去除。

看来只能这样了,从umi文档得知,@umijs/plugin-antd会对antd做按需引入,翻了下源码目前无法配置关闭,我们于是提了个PR,钉钉私聊了期贤,期贤了解了背景后很快合并了PR并发了新版本,非常赞。

接着我们升级了@umijs/preset-react,在.umirc.ts里新增了disableBabelPluginImport配置禁用了按需加载,可喜的是打包后的产物竟然还有一些减少,看来大量使用antd组件时没必要开启按需加载...

antd: {
   disableBabelPluginImport: true
}

应该稳了,发到预发再感受一下...

分页器是好了,但按钮的居然坏了,一调试发现还是因为冗余样式,CSS变量样式被覆盖导致,最终定位到是@ant-design/pro-layout引入的

5、指定pro-layout主题

原因是pro-layou有引用lib|es 目录下的 less 文件,按antd文档所说,需要在less中注入@root-entry-name: variable变量,在.umirc.ts中配置如下:

theme: {
   'root-entry-name': 'variable'
}

再次发到预发,OK,全妥了!

注意在theme里配置了'root-entry-name': 'variable'后,不能再配置'primary-color'的值

四、总结

可以看到,基于CSS变量的动态主题方案还是比较简单的,对于已经使用less的项目,接入成本很低,方案轻量好拓展,并且antd也已经给了官方支持,进一步降低了使用成本,相信以后会成为更多人的选择。

拓展阅读:
CSS 变量教程:https://www.ruanyifeng.com/blog/2017/05/css-variables.html
Element-ui换肤方案:https://github.com/ElemeFE/element/issues/3054
聊一聊前端换肤:https://segmentfault.com/a/1190000018593994

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容