如何切换主题色
首先抛个问题,我们根据什么来辨别同类型的网站?
我觉得是网站的“主题色”,试想下把淘宝的橘红色换成京东的红色,是不是就和后者一样了。
可以说:主题色作为网站辨识度最高的设计元素之一。
实际工作场景中,一个 web 项目可能改个主题色及页面内容,就可以给不同业务方使用。对于如今的前端开发来说,即使有着成熟的 UI 组件框架,比如 Ant Design ,Element UI 等,
但为了变出不同主题色系的网站风格,我们往往会创建新的主题样式来覆盖原有的 UI ,工作量仍然巨大,稍有疏忽就会有遗漏。
那有没有省时省力的方式来定制化 UI 组件色系呢?
肯定有。下面就举例 Ant Design Pro (一款基于 Vue 和 Ant Design 的中后台前端框架),看下它是基于什么实现的?
Ant Design Pro 到底有什么魔法?
我们通过 npm run serve 启动项目,进入主页后,在页面右侧的设置按钮来切换主题色,能看到页面颜色也跟着发生了改变:
试着找些明面上能看到的东西:
打开调试工具,重新刷新页面,看到浏览器发送了一个 /css/theme-colors-xxx.css 的请求,响应内容为一大段样式代码:
审查页面有关主题颜色的元素,发现利用了 样式覆盖 的特性让新颜色起了效果:
这些新的样式代码被定义在一个 style 标签内:
如此,将覆盖原先 UI 组件的颜色样式,从而将达到主题色切换的目的。不知所以然的你,肯定就会有如下问题:
- css 样式代码根据什么规则生成的?
- 请求 /css/theme-colors-xxx.css 哪里发起的?
- 主题色的切换,页面怎么快速做样式更新的?
这些都围绕着 webpack-theme-color-replacer 展开,下面就进入代码来一探究竟。
主题样式代码生成规则?
翻阅 Ant Design Pro 中 webpack 配置,发现有个和主题颜色配置相关的 plugins 逻辑:
1 | // vue.config.js |
createThemeColorReplacerPlugin 返回的就是 webpack-theme-color-replacer 插件对象:
1 | const ThemeColorReplacer = require("webpack-theme-color-replacer"); |
注意到:themePluginOption 中的 fileName 和主题样式请求地址一样(和问题二有关);另外 matchColors 默认通过 getAntdSerials 把 #1890ff 转化为一组蓝色系的主题色:
1 | [ |
webpack plugins 运行时,则调用 ThemeColorReplacer.apply 方法,并触发 Handler.handler 方法:
1 | class ThemeColorReplacer { |
Handler 中,会先初始化一个文件提取器 AssetsExtractor ,通过内部 extractAssets 方法来提取主题样式代码到 output,最后调用 addToEntryJs 方法,将提取结果加到每个入口文件里:
1 | class Handler { |
AssetsExtractor 内部逻辑过于复杂,代码流程如下:
1 | function AssetsExtractor(options) { |
遍历 compilation.assets 下每个资源内容,通过正则 CssCodeReg 将符合 css 代码的内容抓取出来。这些代码就是图中【绿色】中的内容:
再通过 extractColors 方法逐行解析,得到一个有关主题色的 css 数组:
最后将结果输出到 theme-colors-xxx.css 中。
这里就回答其中一个问题:
css 样式代码根据什么规则生成的?
不能遗漏的是:添加样式代码到入口文件时,相关 themePluginOption 配置将被赋值到 window.__theme_COLOR_cfg 作为入口文件的一部分代码,供客户端使用:
1 | getEntryJs(outputName, assetSource, cssCode) { |
主题样式请求怎么发起?
我们以页面的主题设置为入口,看下 SettingDrawer 组件内部的功能。
首先该组件提供了这些配置选项:
在该项目中,会发现有 config\themePluginConfig.js 配置:
1 | // config\themePluginConfig.js |
如果你熟悉 Ant Design Pro,那么在 动态主题 中也能看到一样的配置,其通过 umi-plugin-antd-theme 进行设置。
这些配置在程序启动时,就挂载至 window.umi_plugin_ant_themeVar 下:
1 | // main.js |
作为页面主题的调色板元数据:
1 | var getThemeList = function getThemeList(i18nRender) { |
当主题颜色修改时,则会触发更新主题方法 updateTheme() :
1 | function handleChangeSetting(key, value, hideMessageLoading) { |
updateTheme 会调用 themeColor.changeColor 方法,生成新的主题色系 newColors ,再交给 webpack-theme-color-replacer/client 处理:
1 | export var themeColor = { |
webpack-theme-color-replacer/client 中定义了如何更改主题色的逻辑,注意这里会用到前面 plugins 中定义的 __theme_COLOR_cfg 变量,如此确认了主题色系 oldColors 和样式请求地址 cssUrl:
1 | // node_modules\webpack-theme-color-replacer\client\themeColorChanger.js |
在 setCssText 方法中,会去寻找 id 为 css_xxx 的 style 标签,并调用 getCssString() 方法:
1 | function setCssText(last, url, resolve, reject) { |
getCssString() 内通过 xhr 来发动主题样式文件的请求。从而回答了第二个问题:
请求 /css/theme-colors-xxx.css 哪里发起的?
1 | module.exports = { |
调用完 getCssString 后,将得到 cssText 代码。然后通过 setCssTo 替换新老颜色,这就回答了最后个问题:
主题色的切换,页面怎么快速做样式更新的?
1 | function setCssTo(cssText) { |
这样有个好处,有关主题颜色的样式文件只在首次加载,后续通过替换 style 标签内容,从而达到主题切换。
总结
webpack-theme-color-replacer 是个很小众的库,github 才 200 个 Star。但它的确解决了产品上某些问题。
有时候我们每天忙碌于业务代码的“搬砖”中,枯燥乏味。作为一个软件程序员,除了完成需求外,还需要更多的思考业务,来促使代码有更多的扩展性。
如果你说业务固定不变,或者离我太远,也可以发现开发中的“重复性劳动”,将 ctrl C/V 最大程度地程序化。什么页面可视化搭建,低代码平台可不光光是 KPI 产物,我觉得它们能促使开发人员有更多时间思考,去挖掘更高的价值。
最后,还是需要不断纵向学习,本篇只简单说了该插件的工作过程。但它内部有关生成调色板的逻辑,怎么解析 css 代码,怎么和 webpack 融合都没有呈现出来,更多需要你去深究,别想用到时方恨少。