CSS 变量 - 为何应关注?

CSS 变量(更准确地称为 CSS 自定义属性)将在 Chrome 49 中推出。它们对于减少 CSS 中的重复非常有用,也可用于实现强大的运行时效果,例如主题切换以及可能扩展/填充未来的 CSS 功能。

CSS 杂乱

在设计应用时,常见做法是预留一组要重复使用的品牌颜色,以保持应用的外观一致。遗憾的是,在 CSS 中一遍又一遍地重复这些颜色值不仅费力,而且容易出错。如果在某个时候需要更改其中一种颜色,您可以不顾一切地“查找并替换”所有内容,但对于足够大的项目,这很容易造成危险。

最近,许多开发者都转而使用 SASS 或 LESS 等 CSS 预处理器,这些预处理器通过使用预处理器变量来解决此问题。虽然这些工具极大地提高了开发者的生产力,但它们使用的变量存在一个重大缺点,即它们是静态的,无法在运行时更改。添加在运行时更改变量的能力不仅为动态应用主题设置等功能打开了大门,还对自适应设计具有重大影响,并且有可能为未来的 CSS 功能实现 polyfill。随着 Chrome 49 的发布,这些功能现在以 CSS 自定义属性的形式提供。

自定义属性简述

自定义属性为我们的 CSS 工具箱添加了两个新功能:

  • 作者能够为属性分配任意值,并使用作者选择的名称。
  • var() 函数,允许作者在其他属性中使用这些值。

下面这个简单示例演示了

:root {
    --main-color: #06c;
}

#foo h1 {
    color: var(--main-color);
}

--main-color 是作者定义的自定义属性,值为 #06c。请注意,所有自定义媒体资源都以两个短划线开头。

var() 函数会检索自定义属性值并将其自身替换为自定义属性值,从而生成 color: #06c;。只要自定义属性是在样式表中的某个位置定义的,就应该可供 var 函数使用。

乍一看,语法可能有点奇怪。许多开发者都会问:“为什么不直接使用 $foo 作为变量名称?”此方法之所以被选用,是因为它尽可能灵活,并且未来可能会支持 $foo 宏。如需了解背景信息,您可以阅读规范作者 Tab Atkins 的这篇博文

自定义属性语法

自定义属性的语法非常简单。

--header-color: #06c;

请注意,自定义属性区分大小写,因此 --header-color--Header-Color 是不同的自定义属性。虽然从表面上看它们可能很简单,但允许使用的自定义属性的语法实际上非常宽松。例如,以下是有效的自定义属性:

--foo: if(x > 5) this.width = 10;

虽然它无法用作变量,因为它在任何常规属性中都是无效的,但它可能会在运行时通过 JavaScript 进行读取和处理。这意味着,自定义属性有可能解锁目前 CSS 预处理器无法实现的各种有趣技术。因此,如果您在想“yawn 我已经有 SASS 了,谁在乎…”,请再仔细看看!这些变量并不是您习惯使用的变量。

瀑布

自定义属性遵循标准的级联规则,因此您可以定义具有不同特异性的同一属性

:root { --color: blue; }
div { --color: green; }
#alert { --color: red; }
* { color: var(--color); }
<p>I inherited blue from the root element!</p>
<div>I got green set directly on me!</div>
<div id="alert">
    While I got red set directly on me!
    <p>I’m red too, because of inheritance!</p>
</div>

这意味着,您可以在媒体查询中利用自定义属性来帮助实现响应式设计。一种使用情形是,随着屏幕尺寸的增加,扩大主要版块元素周围的外边距:

:root {
    --gutter: 4px;
}

section {
    margin: var(--gutter);
}

@media (min-width: 600px) {
    :root {
    --gutter: 16px;
    }
}

需要注意的是,目前的 CSS 预处理器无法使用上述代码段,这些预处理器无法在媒体查询中定义变量。拥有这种能力可以释放巨大的潜力!

您还可以创建自定义属性,使其值派生自其他自定义属性。这对于主题设置非常有用:

:root {
    --primary-color: red;
    --logo-text: var(--primary-color);
}

var() 函数

如需检索和使用自定义属性的值,您需要使用 var() 函数。var() 函数的语法如下所示:

var(<custom-property-name> [, <declaration-value> ]? )

其中 <custom-property-name> 是作者定义的自定义属性的名称(例如 --foo),<declaration-value> 是引用的自定义属性无效时要使用的回退值。后备值可以是一个以英文逗号分隔的列表,这些列表将合并为一个值。例如,var(--font-stack, "Roboto", "Helvetica"); 定义了 "Roboto", "Helvetica" 的回退机制。请注意,简写值(如用于外边距和内边距的值)不是以逗号分隔的值,因此适当的内边距回退应如下所示。

p {
    padding: var(--pad, 10px 15px 20px);
}

借助这些后备值,组件作者可以为其元素编写防御性样式:

/* In the component’s style: */
.component .header {
    color: var(--header-color, blue);
}
.component .text {
    color: var(--text-color, black);
}

/* In the larger application’s style: */
.component {
    --text-color: #080;
    /* header-color isn’t set,
        and so remains blue,
        the fallback value */
}

此方法对使用 Shadow DOM 的 Web 组件进行主题设置特别有用,因为自定义属性可以遍历阴影边界。Web 组件作者可以使用后备值创建初始设计,并以自定义属性的形式公开主题“钩子”。

<!-- In the web component's definition: -->
<x-foo>
    #shadow
    <style>
        p {
        background-color: var(--text-background, blue);
        }
    </style>
    <p>
        This text has a yellow background because the document styled me! Otherwise it
        would be blue.
    </p>
</x-foo>
/* In the larger application's style: */
x-foo {
    --text-background: yellow;
}

使用 var() 时,需要注意一些问题。变量不能是属性名称。例如:

.foo {
    --side: margin-top;
    var(--side): 20px;
}

不过,这并不等同于设置 margin-top: 20px;。相反,第二个声明无效,并会作为错误抛出。

同样,您也不能(简单地)构建部分由变量提供的值:

.foo {
    --gap: 20;
    margin-top: var(--gap)px;
}

再次强调,这并不等同于设置 margin-top: 20px;。如需构建值,您需要使用 calc() 函数。

使用 calc() 构建值

如果您之前从未使用过 calc() 函数,不妨了解一下这个实用的小工具,它可以让您执行计算来确定 CSS 值。所有现代浏览器都支持此属性,并且可与自定义属性结合使用来构建新值。例如:

.foo {
    --gap: 20;
    margin-top: calc(var(--gap) * 1px); /* niiiiice */
}

在 JavaScript 中使用自定义属性

如需在运行时获取自定义属性的值,请使用计算的 CSSStyleDeclaration 对象的 getPropertyValue() 方法。

/* CSS */
:root {
    --primary-color: red;
}

p {
    color: var(--primary-color);
}
<!-- HTML -->
<p>I’m a red paragraph!</p>
/* JS */
var styles = getComputedStyle(document.documentElement);
var value = String(styles.getPropertyValue('--primary-color')).trim();
// value = 'red'

同样,如需在运行时设置自定义属性的值,请使用 CSSStyleDeclaration 对象的 setProperty() 方法。

/* CSS */
:root {
    --primary-color: red;
}

p {
    color: var(--primary-color);
}
<!-- HTML -->
<p>Now I’m a green paragraph!</p>
/* JS */
document.documentElement.style.setProperty('--primary-color', 'green');

您还可以在对 setProperty() 的调用中使用 var() 函数,将自定义属性的值设置为在运行时引用其他自定义属性。

/* CSS */
:root {
    --primary-color: red;
    --secondary-color: blue;
}
<!-- HTML -->
<p>Sweet! I’m a blue paragraph!</p>
/* JS */
document.documentElement.style.setProperty('--primary-color', 'var(--secondary-color)');

由于自定义属性可以引用样式表中的其他自定义属性,因此您可以想象这可能会产生各种有趣的运行时效果。

浏览器支持

目前,Chrome 49、Firefox 42、Safari 9.1 和 iOS Safari 9.3 支持自定义属性。

演示

试用此示例,简要了解自定义属性现在可以利用哪些有趣技术。

深入阅读

如果您想要详细了解自定义属性,可以查阅 Google Analytics 团队的 Philip Walton 撰写的入门指南,他为何对自定义属性充满期待。您也可以访问 chromestatus.com,随时查看自定义属性在其他浏览器中的表现。