先看成果
线上预览地址:Dynamic-Antd-Theme-Demo
看起来是不是还可以,如果你知道如何使用,相信你会更觉得我不是一个标题党。// 如何使用
import DynamicAntdTheme from 'dynamic-antd-theme';
render() {
...
...
}
复制代码
没错,就是这么简单,为了方便大家,我已经弄成了即插即用的组件~欢迎大家star + issue + pr。dynamic-antd-theme
为什么要做ant-design的换肤方案
这里是我很想说的,我还没有自负到或者说贪心到有能力去实现一个完美的合理的antd动态换肤方案。比如开源社区里的antd-theme-webpack-plugin和antd-theme-generator就是很成熟的方案,利用webpack的方式在页面里通过引入less.js实现动态换肤,换的很彻底。
那么为什么我还要来搞一个所谓的动态换肤呢?理由如下:
- 首先,上面两个方案并不能覆盖所有的场景,他所需要的入口文件
index.html
,作为nextjs党的我,就怎么找也找不到,所以在我的nextjs脚手架项目里就没有配置成功。 - 其次,个人认为配置以及使用稍微繁琐以及额外引入太多内容。html文件必须引入less.js文件,因为这样才能使用window.less对主题进行动态更换。还有配置的时候各种css文件也是不太清楚意义。
- 最后,也是最重要的。有人在我的文章里提问了,也就是与Next-Antd-Scaffold这个脚手架相关的文章,感兴趣的可以去看看前面我写的文章。小伙伴没有配置成功,又没有思路,怎么办呢,作为热心作者,肯定要帮忙解决呀~
这里安利一波,Next-Antd-Scaffold目前已经基本开发完成了,我个人也在用这个架子写项目,有很多小伙伴也在写项目,应该还可以,对Nextjs感兴趣的同学可以加入文章底部的微信群一起沟通哦~最主要的,你给我一个star,我尽心尽力尽我所能解决问题,?
综上所述,个人花费了周末的时间,搞出来一个即插即用的antd换肤方案,一键安装直接使用。当然了,既然是这么简单,肯定也有弊端,毕竟我的主题是最简单的antd换肤方案而不是最完美的antd换肤方案,详细的听我娓娓道来。
实现过程
具体的实现过程,我将会从思路,解决方案、细节难点以及项目存在的弊端问题来进行说明。
这里我所有的例子都是通过Next-Antd-Scaffold来进行编写的~
思路
先来说思路,既然我想标新立异,最简单的antd的换肤方案,那肯定不能有复杂的配置过程以及表述不清的文档。当然,说实话,在没深读源码以及webpack机制之前,我还没有能力写出上面两个的那种水平的插件,哈哈?。
所以,就只能另辟蹊径了。我的想法就是,能不能在系统运行过程中,通过类覆盖的方式来动态修改颜色,因为CSS加载机制是由上至下,同名会覆盖对应属性嘛,利用这个特性就来简单的尝试一下。
以button按钮为例,可以看到,我们设置的@primaryColor = #524c1a
,类名是.ant-btn-primary
,那么我们来进行覆盖。
// 样式变换代码
const styleDom = document.createElement('style');
styleDom.innerHTML = `
.ant-btn-primary {
border-color: #0000ff;
background-color: #0000ff;
}
`;
document.getElementsByTagName('head')[0].appendChild(styleDom);
复制代码
可以看到,页面在未刷新的前提下,按钮实现了变色。因此,这个思路是可行的,接下来我们要考虑的就是细节问题了。
方案
通过上述实践,我们确定了思路,这里就来确定可行性方案。说实话,类名覆盖这种问题大部分前端开发应该都能想到,应该也有一部分人在用,毕竟换肤的需求是很多中后台系统的基本需求,不过即使是类名覆盖不同人也有不同的做法,而且类名覆盖的难点在于 —— 覆盖基础颜色还好,如果为每一个伪类元素如:hover :active :focus
等都覆盖一个合适的color,不仅实现困难,而且工作量也很大。我这里想的就是,我来给大家实现一个类名覆盖的普适方案,你们再也不需要繁琐的一个页面一个页面去实现,或者各种修改css文件,只需要引入这个插件,自动把所有的类都覆盖好。
具体实现过程
-
提取antd的所有color相关类
我把
antd-v3.19.0版本
的css文件下载到了本地,大概有2W+行代码,从中我细心耐心的提取了所有@primaryColor
相关颜色(包括:hover :focus :active
)等。因为只保留了类下的color相关属性,所以精简到了900行代码左右。这里就简单截图,不给大家展示了,想看的话地址在这theme.css。
这里重点说明的是,
:root{ ... }
下面的几个colorVar,这也算是设计方案吧,因为这样,我只需要获取用户设置的颜色,然后生成相关颜色替换colorVar变量即可。不然的话我需要写一个正则,匹配所有的color,效率肯定没这个好 -
动态获取用户颜色,然后进行替换
这里其实很简单,就是改造我们要插入的style标签内容,具体代码如下:
const cssVar = ` :root { --primary-color: ${primaryColor}; --primary-hover-color: ${hoverColor}; --primary-active-color: ${activeColor}; --primary-shadow-color: ${shadowColor}; } `; // 给插入的标签赋id,避免多次插入
,cssVar
是我们定义的颜色相关的几个变量,然后cssContent
就是我提取的所有css代码toString()
一下成为字符串变量。动态获取颜色这一块,我使用的是
react-color
这个插件,然后既然是换肤,肯定应该保存用户选择方案,所以搭配的就是localStorage
进行客户端缓存,最后效果就是这样:
有人可能说了,你说了半天
@primaryColor
相关颜色,到现在还是只有一个@primaryColor
,是不是在这扯犊子呢,?别急,我都说了是最重要的地方,肯定是放在难点里了。
难点
这里值得跟大家分享一下,难点并不是从两万多行代码里抽离出所有与@primaryColor
相关的属性,而是你需要通过选中的@primaryColor
来动态生成浅颜色的@primaryHoverColor
和深颜色的@primaryActiveColor
。
这里使用过的人应该都明白我的意思,antd的button按钮,a标签等等,
:hover/:focus/:active/:visited
等等这些属性都拥有自己的颜色,一些是与@primaryColor
相比更浅,一些是与@primaryColor
相比更深~具体看下面的动图,hover的时候颜色更浅一些,active的时候颜色更深一些
你不能单纯的把所有颜色相关属性统一变成一个颜色,虽然那样很简单,但是从体验上来讲就失去了一些用户体验感,那样的话还不如不做换肤了。所以接下来就详细说一下这块的实现过程。
去看antd的源码可以发现,他其实并没有相关:hover :active :focus
颜色的详细设置,而是所有颜色都是通过@primaryColor
转换而来的。
可以看到,它将@primaryColor
分成了是个颜色级别,以level6
作为分界线,<6
的颜色会相比@primaryColor
浅一些,适合:hover
这种,>6
的颜色会比@primaryColor
深一些,适合:active
这种。所有颜色都是通过colorPalette
这个方法进行生成的,所以我们详细要说的就是这一块。
事先声明以及甩锅,我尽力去理解去尝试了,不过最后我实现的也只是简单的四种颜色,并没有像原来那样分成10个级别,不喜勿喷,欢迎感兴趣的提PR,弄的越来越好~
/**
* 下面这些代码谨代表我个人的事先过程以及能力水平
* 我没仔细看官方实现,所以antd肯实现的更高级
**/
// 获取hover的浅颜色
function getHoverColor (color, index = 5) {
return tinycolor.mix(
'#ffffff',
color,
currentEasing(index) * 100 / primaryEasing
).toHexString();
}
// 获取active的深颜色
function getActiveColor (color, index = 7) {
return tinycolor.mix(
'#333333',
color,
(1 - (currentEasing(index) - primaryEasing) / (1 - primaryEasing)) * 100
).toHexString();
}
复制代码
上面有两个函数,一个是获取浅颜色,一个是获取深颜色。两个函数内部调用的都是一个叫做tinycolor.mix
的方法,并且我们看一下参数就非常容易理解了,这个mix
方法其实就是让我们的主色@primaryColor
跟另一个颜色去融合,比如跟#ffffff
去融合,即使我不懂代码不懂计算机,学过画画的应该也都知道,如果有颜色的跟白色的混合,颜色会变浅,但是不会变成其他色系,也就是蓝色 -> 浅蓝色,红色 -> 浅红色
等等,另一个也同理,跟#000000
等黑灰色系融合,就会加深。接下来就看这个mix函数了
:
/* tinycolor-mix */
tinycolor.mix = function(color1, color2, amount) {
amount = (amount === 0) ? 0 : (amount || 50);
var rgb1 = tinycolor(color1).toRgb();
var rgb2 = tinycolor(color2).toRgb();
var p = amount / 100;
var rgba = {
r: ((rgb2.r - rgb1.r) * p) + rgb1.r,
g: ((rgb2.g - rgb1.g) * p) + rgb1.g,
b: ((rgb2.b - rgb1.b) * p) + rgb1.b,
a: ((rgb2.a - rgb1.a) * p) + rgb1.a
};
return tinycolor(rgba);
};
复制代码
这里就是将两个颜色和一个权重输入进去,最后输出一个rgba的颜色值。这里面的tinycolor是一个颜色相关的插件,感兴趣的可以去看看。
ok,然后那个权重又是什么东西啊,这里说实话我数学不是很好,就真看不懂了,反正他是一个贝叶斯曲线,就是为了让我们的颜色变换的更平滑~其他文章的解释大概也就是这样了,还有个图片:
这里更加深入的我就不说了,也说不明白,反正我是照猫画虎画出来的。需要强调一下的是曲线需要选中一个基线,antd的基线是如下:/* basic-easiing */
const baseEasing = BezierEasing(0.26, 0.09, 0.37, 0.18);
// 主色基线
const primaryEasing = baseEasing(0.6);
// 融合颜色的基线
const currentEasing = index => baseEasing(index * 0.1);
复制代码
也不知道我讲没讲清楚,上述繁杂的一系列操作过后,你就能根据你输入的主色生成对应的相关主色系颜色值,然后进行cssVar替换即可~
其他特性
你可以通过如下方式进行直接使用:
npm install dynamic-antd-theme
or
yarn add dynamic-antd-theme
复制代码
组件可设置属性如下:
组件使用起来确实称得上史上最简单了,最后的效果说实话也超出了我最初的想法,真的还挺不错的。弊端和遗留问题
- 能力精力有限,只做了
@primaryColor
相关的覆盖 - 其他覆盖仍然需要:global或者组件内覆盖这种方式
- 没有做所有组件的效果测试,可能存在某些场景效果出现偏差,提issue会及时解决
- 会向项目内暴力插入一段900行左右的
标签
紧急更新
颜色计算反馈
这篇文章发布之后,评论区出现了蚂蚁金服的伙伴,给出了人家已经开源的color计算插件,哈哈,我这费时费力的,就当是自己研究了一遍吧。后续可能会替换成ant-design官方计算出来的color,为了更接近原版~**
部分局限性问题
这里要说的就是,比如我设置了主题色,antd组件样式的主题色确实修改了,但是如果是我本地的css样式,其实是没有被修改的。比如我有一个Header组件,初始化背景色跟主题色一致,但是我更换主题色的时候,只变换了antd组件的主题色,并没有更换我这个Header组件的主题色,这样看起来就很突兀~如下所示:
更为详细的请见下方评论,感谢 @myran 掘友的点评
得知这个场景之后,我想到了解决方案,动态添加了一个属性:themeChangeCallback
,传入一个函数,参数是改变后的主题色,做一些覆盖我们本地样式的内容就可以了~
// 主题色修改过后把系统名称的背景色更换
themeChangeCallback(color) {
document.getElementById('sys_name').style.backgroundColor = color;
}
...
'bottomLeft'
themeChangeCallback={this.themeChangeCallback}
/>
复制代码
嗯,这样看起来就完美多了,还是希望大家多提意见,如果自己有想法就PR,没想法发现问题就评论或直接issue。都是可以哒~现在最新版是 v0.1.6
。
总结
有的人看完可能觉得没什么技术含量,就是做了很多重复大量的css操作来覆盖类名而已,说实话,我也承认,但是我觉得有一句名言说的好:这个世界上本没有路,走的人多了,就变成了路。
这个方案也同样如此,每个人都愿意在自己的项目里进行大量的复杂类替换,却不愿意嫌麻烦去弄一套通用antd覆盖类文件,而我只是愿意把路给大家走出来,仅此而已。并不是有什么技术含量的插件,我也承认,哈哈?。
另外,希望大家能多给star,多提issue,为什么呢,因为想做好的话一个人肯定是能力有限的,不可能把所有组件所有场景都是适配好,如果大家在使用的时候能告诉我哪个版本那个组件效果不对了,我可以及时的修改上线。另外如果感兴趣,也可以多提pr,一起维护。当前版本支持 antd version <= 3.19.0的绝大多数组件效果。
如有问题,及时联系,谢谢?。点star不迷路dynamic-antd-theme
Next.js小交流群地址: