前端开发者学堂 - fedev.cn

深入学习CSS自定义属性(CSS变量)

发布于 静子

我本来觉得应该从介绍CSS中引入变量的用途开始讲起,但事实上,很多流行的CSS预处理/后处理程序已经做了很好的诠释。

一些常见的例子:

  • 为风格统一而使用颜色变量
  • 一致的组件属性(布局,定位等)
  • 避免代码冗余

当然,手动地去搜索或者查找/替换依然能满足你的需求,但这就像是不在JS中使用变量一样--很痛苦。事实上,CSS变量的动态性和作用域能够为你的实践和应用提供更加强大的能力--你可以非常高效的读取、设置和更新这些变量!同时,就像Webkit的小伙伴们在审查工具中做的那样,你也可以避免在你的代码中出现重复的代码片段(冗余)。

最后,你可以更方便的从CSS向JS传递数据(例如媒体断点)

以下几点是未来CSS属性的简短说明:

  • 动态性,可以在运行时更改
  • 可以方便的从JS中读/写
  • 可继承,可组合,同时具有作用域

所以,下面就来一起深入了解一下这个CSS属性,同时学习怎么使用它。

名称

一开始我们使用“CSS变量”的称呼,但后来经过扩展和重构,我们开始叫它“CSS自定义属性”。

很显然,更为精确的CSS属性名称更容易呈现其自身特征/语法。现在,我们为“混合”提供了相关的CSS @apply 规则

目前名称为CSS 自定义属性级联变量:

当前,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。这样的设计在使用时非常灵活--你可以使用一些来自某个框架的变量(通常大部分变量定义在这里),同时当你想要移除它的时候,可以保持现有功能正常工作。

作用域

正如文档中提到的:自定义属性遵循CSS级联规则

使用: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自定义属性的方法。CSS:

@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”方法即将到来,这无疑为未来带来曙光。但即便如此绝大多数的变量语法依然不能很好的支持。

但目前为止,下面这些依然是值得关注的:

和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-custom-props-theme-switcher

当然,作为仿真错过单独 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
    );
}

css-colors-from-custom-props

有趣的事实:

  • 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