使用 CSS 自定义属性(变量)

自定义属性(有时也称 CSS 变量级联变量)是由 CSS 作者定义的实体,表示在整个文档中可重用的特定值。它们通过 @property @ 规则或自定义属性语法(例如:--primary-color: blue;)设置。自定义属性可以使用 CSS 的 var() 函数进行访问(例如:color: var(--primary-color);)。

复杂的网站含有大量的 CSS,通常会导致许多重复的 CSS 值。例如,在样式表中,同一个颜色在数百个不同的地方使用是很常见的。要更改一个在多处重复的颜色,需要在所有规则和 CSS 文件中进行查找和替换。自定义属性允许一个值在一个地方定义,然后在多个其他地方引用,从而使其更易于使用。另一个好处是可读性和语义化。例如,--main-text-color 比十六进制颜色 #00ff00 更容易理解,尤其是在不同上下文中使用该颜色时。

使用两个破折号(--)定义的自定义属性受层叠规则的影响,并从其父元素继承其值。@property @ 规则允许对自定义属性进行更多控制,并允许你指定它是否从父元素继承值、初始值是什么以及应应用的类型约束。

注意:变量在媒体查询和容器查询中不起作用。你可以在元素上任何属性值的任何部分使用 var() 函数。但你不能将 var() 用于属性名、选择器或除属性值之外的任何东西,这意味着你不能在媒体查询或容器查询中使用它。

声明自定义属性

在 CSS 中,你可以使用两个破折号作为属性名称的前缀来声明自定义属性,或者使用 @property @ 规则。以下部分将介绍如何使用这两种方法。

使用两个破折号(--)作为前缀

以两个破折号为前缀的自定义属性以 -- 开头,后跟属性名称(例如 --my-property)和可以是任何有效的 CSS 值的属性值。与任何其他属性一样,它写在规则集内部。以下示例展示了如何创建一个自定义属性 --main-bg-color,并使用 <named-color>brown

css
section {
  --main-bg-color: brown;
}

规则集的选择器(上例中的 <section> 元素)定义了自定义属性可以使用的作用域。因此,通常的做法是在 :root 伪类上定义自定义属性,这样就可以全局引用它。

css
:root {
  --main-bg-color: brown;
}

情况并非总是如此:你可能有充分的理由限制自定义属性的作用域。

注意:自定义属性名称区分大小写——--my-color--My-color 将被视为两个独立的自定义属性。

使用 @property @ 规则

@property @ 规则允许你更具表现力地定义自定义属性,可以为属性关联类型、设置默认值和控制继承。以下示例创建了一个名为 --logo-color 的自定义属性,它需要一个 <color>

css
@property --logo-color {
  syntax: "<color>";
  inherits: false;
  initial-value: #c0ffee;
}

如果你想在 JavaScript 中而不是直接在 CSS 中定义或使用自定义属性,有一个相应的 API 可以实现此目的。你可以在 CSS 属性和值 API 页面上了解其工作原理。

使用 var() 引用自定义属性

无论你选择哪种方法定义自定义属性,都可以在 var() 函数中引用该属性,以替代标准的属性值。

css
details {
  background-color: var(--main-bg-color);
}

自定义属性入门

让我们从一些我们想要应用样式的 HTML 开始。有一个作为容器的 <div>,其中包含一些子元素,有些还有嵌套元素。

html
<div class="container">
  <div class="one">
    <p>One</p>
  </div>
  <div class="two">
    <p>Two</p>
    <div class="three">
      <p>Three</p>
    </div>
  </div>
  <input class="four" placeholder="Four" />
  <textarea class="five">Five</textarea>
</div>

我们将使用以下 CSS 根据元素的类来为它们设置样式(下面未显示一些布局规则,以便我们专注于颜色)。根据它们的类,我们给元素设置 tealpink 的背景色。

css
/* For each class, set some colors */
.one {
  background-color: teal;
}

.two {
  color: black;
  background-color: pink;
}

.three {
  color: white;
  background-color: teal;
}

.four {
  background-color: teal;
}

.five {
  background-color: teal;
}

这会产生以下结果:

我们可以使用自定义属性来替换这些规则中重复的值。在 .container 作用域中定义了 --main-bg-color 并在多处引用其值后,更新后的样式如下所示:

css
/* Define --main-bg-color here */
.container {
  --main-bg-color: teal;
}

/* For each class, set some colors */
.one {
  background-color: var(--main-bg-color);
}

.two {
  color: black;
  background-color: pink;
}

.three {
  color: white;
  background-color: var(--main-bg-color);
}

.four {
  background-color: var(--main-bg-color);
}

.five {
  background-color: var(--main-bg-color);
}

使用 :root 伪类

对于某些 CSS 声明,可以在层叠的更高层级声明,让 CSS 继承来解决这个问题。但对于非小型项目,这并不总是可行的。通过在 :root 伪类上声明自定义属性,并在整个文档中需要的地方使用它,CSS 作者可以减少重复的需要。

css
/* Define --main-bg-color here */
:root {
  --main-bg-color: teal;
}

/* For each class, set some colors */
.one,
.three,
.four,
.five {
  background-color: var(--main-bg-color);
}

.two {
  color: black;
  background-color: pink;
}

这会产生与前一个示例相同的结果,但允许对所需属性值进行一次权威性声明(--main-bg-color: teal;),这在你以后想在整个项目中更改该值时非常有用。

自定义属性的继承

使用两个破折号 -- 而非 @property 定义的自定义属性总是继承其父元素的值。下面的示例演示了这一点:

html
<div class="one">
  <p>One</p>
  <div class="two">
    <p>Two</p>
    <div class="three"><p>Three</p></div>
    <div class="four"><p>Four</p></div>
  </div>
</div>
css
div {
  background-color: var(--box-color);
}

.two {
  --box-color: teal;
}

.three {
  --box-color: pink;
}

var(--box-color) 的结果取决于继承,如下所示:

  • class="one"无效值,这是以这种方式定义的自定义属性的默认值。
  • class="two"teal
  • class="three"pink
  • class="four"teal(从其父元素继承)

以上示例展示了自定义属性的一个方面:它们的行为与其它编程语言中的变量不完全相同。值是在需要时计算的,而不是存储起来并在样式表的其它地方重用。例如,你不能设置一个属性的值,并期望在兄弟元素的后代规则中检索到该值。该属性仅为匹配的选择器及其后代设置。

使用 @property 控制继承

@property @ 规则允许你明确声明属性是否继承。以下示例使用 @property @ 规则创建一个自定义属性。继承被禁用,定义了 <color> 数据类型,并设置了初始值为 teal

父元素将 --box-color 设置为 green,并使用 --box-color 作为其背景色的值。子元素也使用 background-color: var(--box-color),如果启用了继承(或者如果它是用双破折号语法定义的),我们期望它的颜色是 green

html
<div class="parent">
  <p>Parent element</p>
  <div class="child">
    <p>Child element with inheritance disabled for --box-color.</p>
  </div>
</div>
css
@property --box-color {
  syntax: "<color>";
  inherits: false;
  initial-value: teal;
}

.parent {
  --box-color: green;
  background-color: var(--box-color);
}

.child {
  width: 80%;
  height: 40%;
  background-color: var(--box-color);
}

因为在 @ 规则中设置了 inherits: false;,并且在 .child 作用域内没有声明 --box-color 属性的值,所以使用了初始值 teal,而不是本应从父元素继承的 green

自定义属性的回退值

你可以使用 var() 函数为自定义属性定义回退值,也可以使用 @property @ 规则的 initial-value

注意:回退值不用于修复 CSS 自定义属性不受支持时的兼容性问题,因为在这种情况下回退值也无济于事。回退涵盖了浏览器支持 CSS 自定义属性,并且在所需变量尚未定义或值无效时能够使用不同值的情况。

var() 函数中定义回退值

使用 var() 函数,你可以在给定变量尚未定义时定义多个回退值;这在处理自定义元素Shadow DOM 时非常有用。

函数的第一个参数是自定义属性的名称。函数的第二个参数是一个可选的回退值,当引用的自定义属性无效时,该值将用作替代值。该函数接受两个参数,将第一个逗号之后的所有内容都赋给第二个参数。如果第二个参数无效,回退将失败。例如:

css
.one {
  /* Red if --my-var is not defined */
  color: var(--my-var, red);
}

.two {
  /* pink if --my-var and --my-background are not defined */
  color: var(--my-var, var(--my-background, pink));
}

.three {
  /* Invalid: "--my-background, pink" */
  color: var(--my-var, --my-background, pink);
}

如上面第二个示例所示(var(--my-var, var(--my-background, pink))),将一个自定义属性作为回退值是使用 var() 提供多个回退的正确方法。但是,你应该注意这种方法的性能影响,因为它需要更多时间来解析嵌套的变量。

注意:回退的语法与自定义属性的语法一样,允许使用逗号。例如,var(--foo, red, blue) 定义了一个 red, blue 的回退值——第一个逗号和函数结束之间的任何内容都被视为回退值。

使用 @property 初始值作为回退

除了使用 var(),在 @property @ 规则中定义的 initial-value 也可以作为一种回退机制。事实上,我们已经在 @property 继承部分看到过这一点。

以下示例使用 @property @ 规则将 --box-color 的初始值设置为 teal。在 @ 规则之后的规则集中,我们想将 --box-color 设置为 pink,但值名中有一个拼写错误。第三个 <div> 也是如此,我们为一个需要有效 <color>的自定义属性使用了 2rem2rempeenk 都是无效的颜色值,因此应用了初始值 teal

css
@property --box-color {
  syntax: "<color>";
  initial-value: teal;
  inherits: false;
}

.one {
  --box-color: pink;
  background-color: var(--box-color);
}

.two {
  --box-color: peenk;
  background-color: var(--box-color);
}

.three {
  --box-color: 2rem;
  background-color: var(--box-color);
}

无效的自定义属性

每个 CSS 属性都可以被赋予一组定义的。如果你试图为一个属性赋予一个超出其有效值集合的值,它将被视为无效

当浏览器遇到一个常规 CSS 属性的无效值时(例如,为 color 属性设置值为 16px),它会丢弃该声明,元素将被赋予它们在没有该声明时应有的值。在下面的例子中,我们看到当一个常规 CSS 声明无效时会发生什么;color: 16px; 被丢弃,而之前的 color: blue 规则被应用。

html
<p>This paragraph is initially black.</p>
css
p {
  font-weight: bold;
  color: blue;
}

p {
  /* oops, not a valid color */
  color: 16px;
}

然而,当自定义属性的值被解析时,浏览器还不知道它们将在哪里使用,所以它必须将几乎所有的值都视为有效。不幸的是,这些有效的值可以通过 var() 函数符号在可能没有意义的上下文中使用。属性和自定义变量可能导致无效的 CSS 语句,从而引出了计算时有效的概念。

当浏览器遇到一个无效的 var() 替换时,将使用该属性的初始值继承值。这个例子和上一个很像,只是我们使用了一个自定义属性。

浏览器将 --text-color 的值替换 var(--text-color),但 16px 对于 color 来说不是一个有效的属性值。替换后,该属性没有意义,所以浏览器分两步处理这种情况:

  1. 检查 color 属性是否可继承。它是可继承的,但是这个 <p> 没有任何父元素设置了 color 属性。所以我们进入下一步。
  2. 将值设置为其默认初始值,即黑色。
html
<p>This paragraph is initially black.</p>
css
:root {
  --text-color: 16px;
}

p {
  font-weight: bold;
  color: blue;
}

p {
  color: var(--text-color);
}

对于这种情况,@property @ 规则可以通过允许定义属性的初始值来防止意外结果。

html
<p>This paragraph is initially black.</p>
css
@property --text-color {
  syntax: "<color>";
  inherits: false;
  initial-value: teal;
}

:root {
  --text-color: 16px;
}

p {
  font-weight: bold;
  color: blue;
}

p {
  color: var(--text-color);
}

JavaScript 中的值

要在 JavaScript 中使用自定义属性的值,就像使用标准属性一样。

js
// get variable from inline style
element.style.getPropertyValue("--my-var");

// get variable from wherever
getComputedStyle(element).getPropertyValue("--my-var");

// set variable on inline style
element.style.setProperty("--my-var", jsVar + 4);

另见