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 预处理器无法实现的各种有趣技术。因此,如果您想要“打哈欠,我有 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,继续在其他浏览器中跟踪自定义媒体资源的进展。