深入学习CSS自定义属性(CSS变量)
我本来觉得应该从介绍CSS中引入变量的用途开始讲起,但事实上,很多流行的CSS预处理/后处理程序已经做了很好的诠释。
一些常见的例子:
- 为风格统一而使用颜色变量
- 一致的组件属性(布局,定位等)
- 避免代码冗余
当然,手动地去搜索或者查找/替换依然能满足你的需求,但这就像是不在JS中使用变量一样--很痛苦。事实上,CSS变量的动态性和作用域能够为你的实践和应用提供更加强大的能力--你可以非常高效的读取、设置和更新这些变量!同时,就像Webkit的小伙伴们在审查工具中做的那样,你也可以避免在你的代码中出现重复的代码片段(冗余)。
最后,你可以更方便的从CSS向JS传递数据(例如媒体断点)
以下几点是未来CSS属性的简短说明:
- 动态性,可以在运行时更改
- 可以方便的从JS中读/写
- 可继承,可组合,同时具有作用域
所以,下面就来一起深入了解一下这个CSS属性,同时学习怎么使用它。
名称
一开始我们使用“CSS变量”的称呼,但后来经过扩展和重构,我们开始叫它“CSS自定义属性”。
很显然,更为精确的CSS属性名称更容易呈现其自身特征/语法。现在,我们为“混合”提供了相关的CSS @apply
规则。
目前名称为CSS 自定义属性级联变量:
变量,是标识符和可以用任何常规值替代值之间的关联,使用
var()
函数表示法:var(- example-variable)
返回--example-variable
的值 。
自定义属性,这是表单的特殊属性
--*
这里*
表示变量名称。这些用于定义给定变量的值:--example-variable:20px;
是一个CSS声明,使用自定义--*
属性将CSS变量--example-variable
的值设置为20px
。
第一个CSS变量
令人感到惊讶的是,你们可能已经了解或使用过一个CSS变量(可以看做是第一个)—— currentColor
,它并不出名,但仍然可用,并且在所有浏览器工作。
它也有一个作用域,并且能够被重新定义:
:root {
color: red;
}
div {
/* border-color is red */
border: 1px solid currentColor;
}
如果你加上下面代码:
div {
color: black;
}
边框将变成黑色。
CSS变量语法
定义
用这样的方式来声明一个变量:--variable-name: variable-value;
(变量名是大小写敏感的)。变量的值可以是颜色、字符串、多个值的组合等:
:root{
--main-color: #4d4e53;
--main-bg: rgb(255, 255, 255);
--logo-border-color: rebeccapurple;
--header-height: 68px;
--content-padding: 10px 20px;
--base-line-height: 1.428571429;
--transition-duration: .35s;
--external-link: "external link";
--margin-top: calc(2vh + 20px);
}
可能语法看起来有些丑陋,却是有原因的。比如不能使用像$var
这样的变量语法,因为已经被其他的CSS预处理程序使用了。
用法
以这样的方式来使用一个变量: some-css-value: var(--variable-name [, declaration-value]);
p {
margin: var(--p-margin, 0 0 10px);
}
在上面的例子中,如果--p-margin
没有被指定,则使用值:0 0 10px
。这样的设计在使用时非常灵活--你可以使用一些来自某个框架的变量(通常大部分变量定义在这里),同时当你想要移除它的时候,可以保持现有功能正常工作。
作用域
使用:root
作用域来定义全局变量:
:root{
--global-var: 'global';
}
如果想让某个变量只在部分元素/组件下可见,只需要在特定的元素下定义该变量:
<div class="block">
My block is
<div class="block__highlight">awesome</div>
</div>
.block {
--block-font-size: 1rem;
font-size: var(--block-font-size);
}
.block__highlight {
--block-highlight-font-size: 1.5rem;
font-size: var(--block-highlight-font-size);
}
媒体查询也可以提供作用域:
@media screen and (min-width: 1025px) {
:root {
--screen-category: 'desktop';
}
}
下面一个例子来展示伪类下的作用域(例如,:hover
):
body {
--bg: #f00;
background-color: var(--bg);
transition: background-color 1s;
}
body:hover {
--bg: #ff0;
}
由于自定义属性是全局的,为了避免冲突,最好按照统一的约定来命名变量(或者简单的遵循BEM命名法来形成”命名空间“),例如:
:root {
/* main (page-wide) variables */
--main-color: #555;
--main-bg: rgb(200, 200, 200);
/* accordion variables */
--accordion-bg: #f00;
--accordion-font-size: 1.5rem;
--accordion__item-bg: #ded;
}
body {
color: var(--main-color);
background-color: var(--main-bg);
/*...*/
}
变量组合
变量可以和其他变量组合使用,--variable-name: var(--another-variable-name);
:
.block {
--block-text: 'This is my block';
--block-highlight-text: var(--block-text)' with highlight';
}
.block:before {
content: var(--block-text);
}
.block__highlight:before {
content: var(--block-highlight-text); /*This is my block with highlight*/
}
这里有个问题 -- 声明新变量的值不能直接由一个已定义的变量计算而来,但我们可以使用CSS calc()
来代替:
.block {
--block-font-size: 1rem;
}
.block__highlight {
/* DOESN'T WORK */
--block-highlight-font-size: var(--block-font-size)*1.5;
font-size: var(--block-highlight-font-size);
/* WORKS */
font-size: calc(var(--block-font-size)*1.5);
}
对复杂的表达式要格外留心,它们很可能会影响到应用的性能。
值的计算(calc())
正如上文提到的,不能简单的这样使用变量:
padding: var(--spacer)px
但借助calc()
即可以实现上面功能以及其他的计算。看一个简单的例子:
margin: 0 0 calc(var(--base-line-height, 0) * 1rem);
最后,可以随时重置/继承变量的值
CSS自定义属性默认是继承的,在这个例子中,通过重置自定义属性,可以消除模块/组件受到的影响:
.with-reset {
--bgcolor: initial;/* RESETS VALUE */
--color: green;/* CHANGES VALUE */
--border: inherit;/* DOESN'T CHANGE ANYTHING, AS INHERITED BY DEFAULT */
}
在JS中使用原生属性
使用CSS样式声明接口,可以在JS中方便的读/写自定义属性(getPropertyValue
, setProperty
):
// READ
const rootStyles = getComputedStyle(document.documentElement);
const varValue = rootStyles.getPropertyValue('--screen-category').trim();
// WRITE
document.documentElement.style.setProperty('--screen-category', value);
下面是使用自定义属性--screen-category
的例子----screen-category
变量描述了当前屏幕类型,同时在UI上可以被组合使用。
例子中展示了一种简单的调试自定义属性的方法。JS代码:
// GET
alert(
getComputedStyle(document.documentElement).getPropertyValue('--screen-category').trim();
);
// SET
document.documentElement.style.setProperty('--screen-category', 'custom');
// or reassign from an another prop
document.documentElement.style.setProperty(
'--screen-category', 'var(--default-screen-category, '%DEFAULT VALUE IF VAR IS NOT SET%')'
);
CSS变量值的组合能力以及JS提供的方便的读写接口,可以让我们告别老的从CSS/Sass中传数据给JS的hack方式(例如:媒体查询断点列表)。
如果是为了调试, 可以通过content
在页面上输出变量的值:
body:after {
content: '--screen-category : 'var(--screen-category);
}
浏览器支持
CSS自定义属性已经在正式版Chrome,Firefox和桌面版Safari 9.1中获得支持:
同时也在微软Edge浏览器的考虑支持中
目前有一些限制和错误:
- 在一些浏览器中,针对CSS变量的复杂
calc()
运算可能不能工作。 - 在当前作用域下的所有自定义属性上应有公共规则(如,reset)的功能在讨论中。就像这样:
--: initial;
- 不能使用CSS自定义属性作为CSS属性名称:
var(--side): 10px;
- 进行
calc()
运算时,最好能提供默认值:calc(var(--base-line-height, 0) * 1rem)
- 不能作为媒体查询值使用:
@media screen and (min-width: var(--desktop-breakpoint) {
- 图片地址,如
url(var(--image-url))
,不会生效
@supports ( (--a: 0)) {
/* supported */
}
@supports ( not (--a: 0)) {
/* not supported */
}
JS:
if (window.CSS && window.CSS.supports && window.CSS.supports('--a', 0)) {
alert('CSS properties are supported');
} else {
alert('CSS properties are NOT supported');
}
对于老的浏览器(没有CSS.supports()
方法),可以使用Wes Bos的测试方法。
Fallbacks / polyfills
有很多PostCSS插件的实例,但他们的实现没有完全正确而又和标准一致的,更重要的是,他们都不是动态的。
CSS Houdini团队的针对所有主流浏览器的一种简单的原生CSS “polyfills”方法即将到来,这无疑为未来带来曙光。但即便如此绝大多数的变量语法依然不能很好的支持。
但目前为止,下面这些依然是值得关注的:
- PostCSS plugin to transform W3C CSS Custom Properties -- 简单的插件,只能处理:root作用域下的变量声明
- PostCSS插件,用来将CSS自定义属性(CSS变量)转换为静态的表达式 ,还有它的线上实例.
- Myth- 一个预处理器
- cssnext可以让许多CSS的新语法立即能够正常工作。
和CSS预处理器(SCSS)一起使用
相同变量名
有一个小建议,使用浏览器支持检测,来开始CSS自定义属性和预处理器的协同使用:
@supports ( (--a: 0)) {
/* Custom properties are supported in the browser */
:root{
--main-bg: #4d4e53;
}
body {
background-color: var(--main-bg);
}
}
@supports ( not (--a: 0)) {
/* Custom properties are NOT supported in the browser */
$main-bg: #4d4e53;
body {
background-color: $main-bg;
}
}
在这个实例中,CSS变量和Sass变量都被创建了,但只有在浏览器不支持CSS自定义属性时,Sass变量才会生效。
你也可以去除这段逻辑,利用Sass的mixin来隐藏它:
@mixin setVar($varName, $value){
@include setVarSass($varName, $value);
@include setVarCss($varName, $value);
}
@mixin setPropFromVar($propName, $varName){
@supports ( (--a: 0)) {
// Custom properties are supported in the browser
#{$propName}: getVarCss($varName);
}
@supports ( not (--a: 0)) {
// Custom properties are NOT supported in the browser
#{$propName}: getVarSass($varName);
}
}
// SET
@include setVar('main-color', #f00);
// GET
body {
@include setPropFromVar('color', 'main-color');
}
全局变量
变量作用域的相关理念在Sass和CSS中是不同的,这里是一个通用的实现方法:
/* SCSS */
$main-color: #f00 !global;
/* CSS */
:root{
--main-color: #f00;
}
只关联没有被关联使用的变量
一个常见的情况是,当你期望可能已经定义的变量,在未被分配时才被应用到另一个值:
/* SCSS */
$main-color: #f00 !default;
body{
color: $main-color;
}
不幸的是,在CSS中不能简单的就使用它:
/* CSS */
body{
--main-color: var(--main-color, #f00); /* DOESN'T WORK */
}
但是你可以创建一个新变量
/* CSS */
body{
--local-main-color: var(--main-color, #f00); /* DOES WORK */
color: var(--local-main-color);
}
或者在使用的时候这样做:
/* CSS */
body{
color: var(--main-color, #f00); /* DOES WORK */
}
有趣的用法
自定义属性为很多有趣的想法提供了想象空间:
- 现在可以用原生的方式实现CSS和JS的平等对话,而不使用任何hack手段
- 另外一个例子就是为了国际化使用自定义属性,根据所选中语言更改文本链接以及颜色链接
- Jake Archibald使用CSS变量控制可视化元素的建议是根据页面: 文章加载的块以及样式
- 主题切换: 不使用为特定类添加额外CSS规则或者加载额外的CSS文件更改网站样式,我们可以使用自定义属性,这里有Michael Scharnagl所发表的一篇可参考文章。
- 关于如何使用他们,我也有一些自己的观点,如,特定域的品牌(如提供了不同视觉感受的 domain1.site.com 以及 domain1.site.com)。对于这些我们可以轻松上传以及应用一些额外的CSS文件(取决于域),重新定义了一些自定义属性集
最后一个创意以及示例就是基于自定义属性的主题切换,所以你可以在以下情境中使用:
当然,作为仿真错过单独 CSS 属性的用法 —— "自定义属性"在这种情况下也会很适用︰
Demo
受Wes Bos CSS自定义属性精彩演示的启发,我决定使用CSS calc();
从R,G,B频道(用户定义)进行颜色计算。
grayscale
过滤代码如下:
.grayscale {
background-color: rgba(
calc(0.2126 * var(--r)),
calc(0.7152 * var(--g)),
calc(0.0722 * var(--b)),
1
);
}
有趣的事实:
- Chrome 使用CSS变量
calc()
不支持乘/除以非整数 - Firefox不支持
rgba()
内的calc()
自定义属性 - Safari支持Demo演示 :blush:
总结
现在你已经知道了什么是CSS自定义属性,还有:
- CSS和JS的语法级互动
- 动态性、可继承、可组合,同时拥有作用域
- 浏览器支持以及如何可靠地使用
- 可以和Sass变量一起使用
- 自定义变量拓展了开发者和web平台的能力,随之产生了一些有趣的用法和案例
希望在阅读完本文后,能有助于你开启使用CSS 自定义属性的兴奋之旅
扩展阅读
最近,CSS的原生mixins语法已经公布 -- 更多文章:CSS @apply
rule (native CSS mixins)
本文根据@malyw的《CSS custom properties (native variables) In-Depth》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:https://blog.hospodarets.com/css_properties_in_depth。
如需转载,烦请注明出处:https://www.fedev.cn/css3/css-properties-in-depth.htmlAir Max 95 Invigor Print