CSS 变量 - 为何应关注?

CSS 变量(更准确地称为 CSS 自定义属性)将在 Chrome 49 中推出。它们对于减少 CSS 中的重复代码非常有用,对于主题切换和可能扩展/polyfill 未来 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,了解在其他浏览器中推出自定义媒体资源的进展。