前言
其实CSS Variables
并不是最近才出现的新生事物,早在2012年W3C
就已经公布了CSS Variables
的首个公开草案;2017年3月微软_Edge_浏览器也宣布支持 CSS 变量,此时所有主要浏览器都已经支持这个 CSS 新功能。
CSS Variables
本质是定义一系列样式属性,本质上和 color
和 font-size
属性是一样的,只是没有默认含义,且可以被其他属性引用,并且提供了 JavaScript
基层API进行管理。因此 CSS Variables
天生就是为动态主题而生的,能够在运行时直接以简洁明了,灵活的方式调整页面样式。
Antd 在antd@4.17.0-alpha.0
推出了实验性的动态主题方案,就是采用 CSS Variables
的方式来实现的,接下来本文将结合 Antd动态主题 来仔细探究 CSS Variables
和动态主题的内在联系。
本文分为CSS Variables基础和Antd动态主题底层探究两部分,依据循序渐进的方式,先介绍基础原理,再介绍动态主题的底层原理,已经熟悉基础原理的同学请跳过这一部分,直接进入干货部分。
CSS Variables
基础
1、变量声明
声明变量时,变量名之前必须加两个连词线(--
),之所以使用--
这个符号来表示,是因为$
被 Sass 用掉了,@
被 Less 用掉了。
html {--ant-primary-color: #1890ff;--ant-primary-color-hover: #40a9ff;--ant-primary-color-active: #096dd9;--ant-primary-color-outline: rgba(24, 144, 255, 0.2);
}
后续的单词一般采用HTML的属性的声明方式,使用 -
进行链接。
CSS Variables 又叫做CSS 自定义属性,它的值类型和 CSS 属性值的类型是一样的,可以放入字符串、色值、时间等。
变量名大小写敏感,--header-color
和--Header-Color
是两个不同变量。
@keyframes waveEffect {100% {box-shadow: 0 0 0 var(--cmsAnt-primary-color);box-shadow: 0 0 0 6px var(--antd-wave-shadow-color);}
}
@media screen and (min-width: 768px) {body {--primary:#F7EFD2;--secondary: #7F583F;}
}
并且支持在响应式布局@media
和动画@keyframes
中使用。
同样支持变量依赖声明。
html {--antd-wave-shadow-color: var(--ant-primary-color);--scroll-bar: 0;
}
2、var()
函数
var()
是用于读取变量,并且支持第二个参数作为默认值,这是个很棒的设计。
color: var(--foo, #7F583F);
以下是注意事项:
- 1、 读取变量值只能作用于属性值,而不能作用于属性名。
.foo {--side: margin-top;/* 无效 */var(--side): 20px;
}
- 2、可以字符串拼接,但是带单位的变量值不可以拼接
body:after {content: '--screen-category : 'var(--screen-category);
}
假如假如需要带单位,则需要使用calc()
函数。
.foo {--gap: 20;/* 无效 */margin-top: var(--gap)px;/* 有效 */margin-top: calc(var(--gap) * 1px);
}
如果变量值带有单位,就不能写成字符串。
/* 无效 */
.foo {--foo: '20px';font-size: var(--foo);
}
/* 有效 */
.foo {--foo: 20px;font-size: var(--foo);
}
3、作用域
同一个 CSS 变量,可以在多个选择器内声明。读取的时候,优先级最好的声明生效。
<style> html {--color: pink;}:root { --color: blue; }div { --color: green; }#alert { --color: red; }* { color: var(--color); } </style>
<p>蓝色</p>
<div>绿色</div>
<div id="alert">红色</div>
:root
除了优先级更高之外,其他和html
保持一致,因此p
标签生效的是:root
的作用域的变量定义,如果想对动态主题进行强制覆盖,目前动态主题方案一般是使用html
作用域下,则可以使用:root
作用域声明进行强制覆盖。
选择器的优先级则是按照 html
< div
< ID 选择器
。
4、兼容性处理
CSS variables
在一些旧版本主流浏览器或者IE浏览器是无法使用的,可以采用以下写法进行规避。
a {color: #7F583F;color: var(--primary);
}
或者使用@supports
进行检测。
@supports ( (--a: 0)) {/* supported */
}
@supports ( not (--a: 0)) {/* not supported */
}
5、JavaScript 操作
首先 JavaScript 可以检测浏览器是否支持 CSS 变量。
const isSupported =window.CSS &&window.CSS.supports &&window.CSS.supports('--a', 0);
if (isSupported) {/* supported */
} else {/* not supported */
}
JavaScript API 写法。
// 设置变量
document.body.style.setProperty('--primary', '#7F583F');
// 读取变量
document.body.style.getPropertyValue('--primary').trim();
// '#7F583F'
// 删除变量
document.body.style.removeProperty('--primary');
这里补充一些其他文章没有提到的信息。
首先<style>
内定义的样式变量,无法通过 JavaScript API 来获取。
// 在作用域的例子中,执行下面的逻辑
document.querySelector(':root').style.getPropertyValue('--color')
// 返回 ''
document.querySelector(':root').style.setProperty('--color', 'gray')
// 返回 undefined 并且字体颜色变成灰色
document.querySelector(':root').style.getPropertyValue('--color')
// 返回 'gray'
到这里大家就明白了其内在限制了。
Antd 动态主题底层探究
首先看下 Antd 动态主题的文档,最主要的改动是修改引入的样式文件。
-- import 'antd/dist/antd.min.css';
++ import 'antd/dist/antd.variable.min.css';
本文抓取了最终引入的文件内容如下:
html {--ant-primary-color: #1890ff;--ant-primary-color-hover: #40a9ff;--ant-primary-color-active: #096dd9;...
}
...
a {color: var(--ant-primary-color);text-decoration: none;background-color: transparent;outline: none;cursor: pointer;transition: color 0.3s;-webkit-text-decoration-skip: objects;
}
...
这里就显而易见了,底层就是CSS Variables
的设计。下面的语法实际也是对上面的 CSS Variables
的修改。
ConfigProvider.config({ prefixCls: 'custom', theme: { primaryColor: '#25b864', }, });
到这里你是不是会疑惑?CSS Variables
是不是唯一的动态主题实现方案?实际上并不是的,antd-theme-generator
库就是就是基于 Less api
的动态主题方案,有兴趣的可以详细看下我的另外一篇文章《高度兼容低版本的 antd 的动态主题方案》。这篇文章具体介绍antd-theme-generator
库的优缺点以及如何解决其中的问题落实到实际项目中去。
CSS Variables
是不是唯一的动态主题实现方案?
正如上面所说,Less 基础 API 也对样式变量进行了支持,而且相对于CSS Variables
的语法丰富度更高。以 Antd Button 的样式文件为例。
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import './mixin';
@btn-prefix-cls: ~'@{ant-prefix}-btn';
// for compatible
@btn-ghost-color: @text-color;
@btn-ghost-bg: transparent;
@btn-ghost-border: @border-color-base;
// Button styles
// -----------------------------
.@{btn-prefix-cls} {&-primary {.btn-primary();.@{btn-prefix-cls}-group &:not(:first-child):not(:last-child) {border-right-color: @btn-group-border;border-left-color: @btn-group-border;&:disabled {border-color: @btn-default-border;}}}
}
如果你直接在HTML中引入类似上面 less 样式文件,
<script> less = { env: "development" }; </script>
<script src="less.js" data-env="development"></script>
那么你就可以通过下面的语法进行样式动态调整。
less.modifyVars({'@buttonFace': '#5B83AD','@buttonText': '#D9EEF2',
});
只是大家日常为了做到更好的浏览器兼容,没有直接使用 less 样式文件,都是使用经过编译后的 css 文件。
这里还要提到一点就是 Less 命令行支持 Less variables
到 CSS Variables
的转换,这个就相当实用,例如在 Antd 动态主题中提到可以使用以下命令重新生成一份新前缀的 css 文件。
lessc --js --modify-var="ant-prefix=custom" antd/dist/antd.variable.less modified.css
更多的命令可以参考 Less 官网命令行使用。
结语
到这里为止,本文分析了 CSS Variables
和动态主题互为表里的关系,有道是纸上得来终觉浅,绝知此事要躬行,大家可以在实际项目中可以多体验下动态主题的相关方案,毕竟动态主题的用户体验要远远高于传统的主题定制。
就我自己的使用体验而言,相对于传统静态主题定制的方式,动态主题在实际使用过程中也存在以下局限性的。
- 性能差,无法进行压缩、混淆,文件体积大
- 和
css in js
的思想有一定的冲突,无法使用css module
,实际代码开发体验不好 - 不适用于微前端等应用场景,CSS 隔离会遇到很大的麻烦
- 兼容性差
因此在决策是否使用动态主题时,还是要从自身项目的实际需要进行综合考量的,按需选取。
最后
整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。
有需要的小伙伴,可以点击下方卡片领取,无偿分享