背景
- 需求背景:动态配置页面主题相关颜色,而不是定制主题模式(类似黑夜模式,白天模式)。
- 兼容性问题:在 IE 中
var()
报错显示异常成透明色,(css-vars-ponyfill 插件 ,使页面展示一个默认色) - 第三方 UI 库支持 CSS Variable,([email protected] 以上,[email protected] 以上 )
1. 实现原理
1.1. 自定义属性 (--*): CSS 变量
带有前缀 -- 的属性名,比如--example--name
,表示的是带有值的自定义属性,其可以通过var
函数在全文档范围内复用的。
CSS 自定义属性是可以级联的:每一个自定义属性可以多次出现,并且变量的值将会借助级联算法和自定义属性值运算出来。
注意,规则集所指定的选择器定义了自定义属性的可见作用域。通常的最佳实践是定义在根伪类:root
下,这样就可以在 HTML 文档的任何地方访问到它。
:root
这个 CSS 伪类匹配文档树的根元素。对于 HTML 来说,:root
表示 元素,除了优先级更高之外,与
html
选择器相同。
1.2. ConfigProvider.config() (config-provider/index.js)
ConfigProvider.config = setGlobalConfig;
var setGlobalConfig = function setGlobalConfig(_ref) {
var prefixCls = _ref.prefixCls,
iconPrefixCls = _ref.iconPrefixCls,
theme = _ref.theme;
if (prefixCls !== undefined) {
globalPrefixCls = prefixCls;
}
if (iconPrefixCls !== undefined) {
globalIconPrefixCls = iconPrefixCls;
}
if (theme) {
(0, _cssVariables.registerTheme)(getGlobalPrefixCls(), theme); // 更改主题色
}
1.3. registerTheme() (node_modules/mkui-fd/lib/config-provider/cssVariables.js)
创建所有 css 变量,可参考简易版 customCssVariable.js
import { updateCSS } from 'rc-util/lib/Dom/dynamicCSS';
import { TinyColor } from '@ctrl/tinycolor';
import { generate } from '@ant-design/colors';
function setThemeColor({ themeColor, varName }) {
const variables = {};
// ================ Primary Color ================
if (themeColor) {
// 转成 TinyColor 色值格式
const primaryColor = new TinyColor(themeColor);
// 10个不同阶梯色值
const colorPalettes = generate(primaryColor.toRgbString());
// Legacy - We should use semantic naming standard
variables[`${varName}`] = themeColor;
colorPalettes.forEach((color, index) => {
variables[`${varName}-${index + 1}`] = color;
});
}
// Convert to css variables
// cssList ['--theme-color: xxx','--theme-color-1: xxx',...,'--theme-color-10: xxx' ]
const cssList = Object.keys(variables).map(
key => `--${key}: ${variables[key]};`,
);
// updateCSS 更新业务代码中的主题色: 创建 style 标签,值为cssList,并作为最后一个子元素插在head标签下
updateCSS(
'\n :root {\n '.concat(cssList.join('\n'), '\n }\n '),
'-mkui-fd-'.concat(Date.now(), '-').concat('-dynamic-theme'),
);
}
1.4. updateCSS() (node_modules/rc-util/lib/Dom/dynamicCSS.js)
创建 style 标签,值为 cssList,作为最后一个子元素插在 head 标签下,详见 node_modules/rc-util/lib/Dom/dynamicCSS.js
1.5. 动态改变颜色另外一种写法(我们项目中不推荐)
在 body 标签中增加 style 属性,改变 body 中所有 --mkui-primary-color 使用值, 优先级高于 html
document.body.style.setProperty('--mkui-primary-color', '#ffff00');
↓ ↓ ↓ ↓ ↓ ↓
2. 如何使用
2.1 替换当前项目引入样式文件为 CSS Variable 版本,并在 .babel.config 中去除 babel-plugin-import 配置。
{ "libraryName": "antd" }
import { Button } from 'antd';
ReactDOM.render();
↓ ↓ ↓ ↓ ↓ ↓
var button = require('antd/lib/button');
ReactDOM.render();
{ "libraryName": "antd", style: "css" }
import { Button } from 'antd';
ReactDOM.render();
↓ ↓ ↓ ↓ ↓ ↓
var button = require('antd/lib/button');
require('antd/lib/button/style/css');
ReactDOM.render();
{ "libraryName": "antd", style: true }
import { Button } from 'antd';
ReactDOM.render();
↓ ↓ ↓ ↓ ↓ ↓
var button = require('antd/lib/button');
require('antd/lib/button/style');
ReactDOM.render();
注意:
Antd 默认支持基于 ES modules 的 tree shaking,对于 js 部分,直接引入 import { Button } from 'antd'
就会有按需加载的效果。
如今webpack,rollup等构建工具都具备了摇树功能(Tree Shaking), 其原理是利用ESM模块的import语法的特性,通过AST语法树进行分析然后去除未使用到的代码。但tree shaking方式也只是处理了js部分,对于组件的css加载还需要手动引入.
2.2 引入包含css 变量的组件库 css 文件(app.js)
import 'mkui-fd/dist/mkui-fd.variable.min.css';
import 'mkui-ext/dist/mkui-ext.variable.min.css';
2.3.定义颜色变量(variables.less)
- 使用 mkui-fd 组件中已有变量
:root 下已经默认生成了部分可全局使用的变量:--mkui-primary-1, ... --mkui-primary-10
使用 var()
插入 CSS 变量的值
@theme-color: var(--mkui-primary-color, #1890ff);
@theme-color-selected-bg: var(--mkui-primary-1, #f0f8ff);
-
自定义创建可全局使用的 CSS 变量
// color.less
为了兼容 IE 使用 css-vars-ponyfill, 必须使用:root 而不是 html
:root {
--theme-color: #1890ff;
--theme-color-selected-bg: #f0f8ff;
}
// variables.less
@theme-color: var(--theme-color);
@theme-color-selected-bg: var(--theme-color-selected-bg, #f0f8ff);
2.4. 调用配置方法
1) 调用 ConfigProvider 配置方法设置主题色(cssVariable.js):
// 修改 mkui-fd 组件颜色
ConfigProvider.config({
theme: { primaryColor: themeColor },
});
// 修改 mkui-ext 组件颜色
ExtConfigProvider.config({
theme: { primaryColor: themeColor },
});
Note: primaryColor: 组件基本色,successColor
,warningColor
,errorColor
,infoColor
特定情况下的颜色。
2) 对于自定义颜色变量,动态更改自定义变量颜色(customCssVariable.js)
setThemeColor({
themeColor,
varName: 'theme-color'
})
2.5. 色值相关
- 与主题色相关的颜色,设计图中会给一个主色(C6),及对应的10个色阶阶梯的哪一阶
3. 注意点:
-
为什么是
:root
而不是html
CSS 不仅用于样式化 HTML 文档, 它也用于 XML 和 SVG 文件。对于 XML 和 SVG 文件,
:root
不是选择html
元素,而是选择它们的根(例如 SVG 文件中的 svg 标签)。:root
选择器优先级更高有助于将使用的 CSS 变量与项目中使用样式的选择器分开
css-vars-ponyfill 中要求:自定义属性声明支持仅限于
:root
和:host
规则集
-
fade(),dark()
等函数不支持变量参数fade('1199ff', 90%); fade(var(--theme-color), 90%); // 错误使用
mkui-fd
,mkui-ext
以及项目中颜色相关样式,不要使用内联和 !important
- IE 11下取色器组件
[email protected]
报错,降低[email protected]
组件版本
SCRIPT438: Object doesn't support property or method 'contains'
4. 其他扩展点
4.1 polyfill 和 ponyfill 的区别
polyfill 在原有的墙壁上打补丁,ponyfill 的策略则是另起炉灶,不会在原有的墙壁上修补,而是重新建一面墙,保证原来的墙壁还是原始纯净无污染。
例如:Array.isArray()
, 此方法 IE8 浏览器并不支持
polyfill 策略
if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
ponyfill 策略
// 避免使用原生 API
// 基本上,为了避免全局命名的污染,Ponyfill都是建议采用独立的模块化的方式开发与调用的
function isArray (arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
}
什么时候使用 Ponyfill?
- 有些原生 API 完全没法模拟,此时只能使用 Ponyfill 策略,例如 IE9 浏览器无法模拟 indexDB 能力,
history.pushState()
方法 - 有些原生 API 规范还没稳定,或者处于快速迭代中,或者是浏览器部分支持
4.2 css-vars-ponyfill 的使用
作用:
- CSS 自定义属性到静态值的转换
- 现代和旧版浏览器中运行时值的实时更新
- 转换
、
和
@import CSS
- 将相对
url()
路径转换为绝对 URL - 支持链式和嵌套的
var()
函数
...
限制:
- 自定义属性声明支持仅限于
:root
和:host
规则集-
var()
的使用仅限于属性值(根据 W3C 规范)
-
更新值:
- 在旧版浏览器中,ponyfill 将确定哪些
和
中包含 css 变量,然后转换成与旧版兼容的 CSS,并附加到每个元素的 DOM。
- 在支持 CSS 变量的现代浏览器中,ponyfill 将使用
style.setProperty()
接口更新值。
请注意,当options.onlyLegacy
为false
时,支持 CSS 变量的现代浏览器将被视为旧版浏览器。
Note:
{ onlyLegacy: true }
将此值设置为 false
可以在现代浏览器中模仿旧版浏览器,进行测试和调试。
4.3 HSV
基于HSB模型构建色彩体系
1.色相
表示色彩的相貌,也就是我们常说的红、橙、黄、绿等颜色名称。色相值按位置度量,取值为0°~360°,在HSB色彩模型中红色为0°,黄色为60°,绿色为120°,青色为180°,蓝色为240°,品红色为300°。十二色相环每一色相间距30°,二十四色相环每一色相间距15°。
2.饱和度
表示色彩的纯度,取值范围0~100%,从色环中心向外递增。当饱和度为0时点在中心,则显示为灰、白、黑无彩色。当饱和度达到100%时,点则移动到色环边缘,会显示每个色相最纯的色光。如下图所示,在色相(H)、亮度(B)不变的情况下减少饱和度(S)颜色逐渐变淡最后变成白色。S控制混入白色的量。
3.亮度
指色彩的明亮度,取值范围0~100%,沿着圆柱体底部向上递增。亮度为0时即黑色,点处于最底部。当达到100%时点上升到顶端,会显示色相最鲜明的状态。如下图所示,在色相(H)、饱和度(S)不变的情况下减少亮度(B)颜色逐渐变暗最后变成黑色。B控制混入黑色的量。
4.结论
1. 选取一个颜色作为主色(6 号色);
2. 判断减淡或加深,进行颜色混合
- 若减淡,则主色与纯白色(#fff)混合,根据色号,获取贝塞尔曲线上的对应值。
- 若加深,则主色与它对应的深色混合,根据色号,获取贝塞尔曲线上的对应比例值。加深时主色对应的深色进行了明度与色相的调整,其中对色相的调整也就是上述引用中说的“针对冷暖色的旋转”;
3. 分别取1~9色号的色值,得到一条完整渐变色板。
参考文章
AntD 动态主题
基于 HSB 模型构建色彩体系
Ant Design 色板生成算法演进之路
polyfill 和 ponyfill
css-vars-ponyfill 文档