使用 CSS 自定义属性(变量)
自定义属性(有时也称 CSS 变量或级联变量)是由 CSS 作者定义的实体,表示在整个文档中可重用的特定值。它们通过 @property
@ 规则或自定义属性语法(例如:--primary-color: blue;
)设置。自定义属性可以使用 CSS 的 var()
函数进行访问(例如:color: var(--primary-color);
)。
复杂的网站含有大量的 CSS,通常会导致许多重复的 CSS 值。例如,在样式表中,同一个颜色在数百个不同的地方使用是很常见的。要更改一个在多处重复的颜色,需要在所有规则和 CSS 文件中进行查找和替换。自定义属性允许一个值在一个地方定义,然后在多个其他地方引用,从而使其更易于使用。另一个好处是可读性和语义化。例如,--main-text-color
比十六进制颜色 #00ff00
更容易理解,尤其是在不同上下文中使用该颜色时。
使用两个破折号(--
)定义的自定义属性受层叠规则的影响,并从其父元素继承其值。@property
@ 规则允许对自定义属性进行更多控制,并允许你指定它是否从父元素继承值、初始值是什么以及应应用的类型约束。
声明自定义属性
在 CSS 中,你可以使用两个破折号作为属性名称的前缀来声明自定义属性,或者使用 @property
@ 规则。以下部分将介绍如何使用这两种方法。
使用两个破折号(--
)作为前缀
以两个破折号为前缀的自定义属性以 --
开头,后跟属性名称(例如 --my-property
)和可以是任何有效的 CSS 值的属性值。与任何其他属性一样,它写在规则集内部。以下示例展示了如何创建一个自定义属性 --main-bg-color
,并使用 <named-color>
值 brown
。
section {
--main-bg-color: brown;
}
规则集的选择器(上例中的 <section>
元素)定义了自定义属性可以使用的作用域。因此,通常的做法是在 :root
伪类上定义自定义属性,这样就可以全局引用它。
:root {
--main-bg-color: brown;
}
情况并非总是如此:你可能有充分的理由限制自定义属性的作用域。
注意:自定义属性名称区分大小写——--my-color
和 --My-color
将被视为两个独立的自定义属性。
使用 @property
@ 规则
@property
@ 规则允许你更具表现力地定义自定义属性,可以为属性关联类型、设置默认值和控制继承。以下示例创建了一个名为 --logo-color
的自定义属性,它需要一个 <color>
。
@property --logo-color {
syntax: "<color>";
inherits: false;
initial-value: #c0ffee;
}
如果你想在 JavaScript 中而不是直接在 CSS 中定义或使用自定义属性,有一个相应的 API 可以实现此目的。你可以在 CSS 属性和值 API 页面上了解其工作原理。
使用 var()
引用自定义属性
无论你选择哪种方法定义自定义属性,都可以在 var()
函数中引用该属性,以替代标准的属性值。
details {
background-color: var(--main-bg-color);
}
自定义属性入门
让我们从一些我们想要应用样式的 HTML 开始。有一个作为容器的 <div>
,其中包含一些子元素,有些还有嵌套元素。
<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 根据元素的类来为它们设置样式(下面未显示一些布局规则,以便我们专注于颜色)。根据它们的类,我们给元素设置 teal
或 pink
的背景色。
/* 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
并在多处引用其值后,更新后的样式如下所示:
/* 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 作者可以减少重复的需要。
/* 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
定义的自定义属性总是继承其父元素的值。下面的示例演示了这一点:
<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>
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
。
<div class="parent">
<p>Parent element</p>
<div class="child">
<p>Child element with inheritance disabled for --box-color.</p>
</div>
</div>
@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 时非常有用。
函数的第一个参数是自定义属性的名称。函数的第二个参数是一个可选的回退值,当引用的自定义属性无效时,该值将用作替代值。该函数接受两个参数,将第一个逗号之后的所有内容都赋给第二个参数。如果第二个参数无效,回退将失败。例如:
.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>
值的自定义属性使用了 2rem
。2rem
和 peenk
都是无效的颜色值,因此应用了初始值 teal
。
@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
规则被应用。
<p>This paragraph is initially black.</p>
p {
font-weight: bold;
color: blue;
}
p {
/* oops, not a valid color */
color: 16px;
}
然而,当自定义属性的值被解析时,浏览器还不知道它们将在哪里使用,所以它必须将几乎所有的值都视为有效。不幸的是,这些有效的值可以通过 var()
函数符号在可能没有意义的上下文中使用。属性和自定义变量可能导致无效的 CSS 语句,从而引出了计算时有效的概念。
当浏览器遇到一个无效的 var()
替换时,将使用该属性的初始值或继承值。这个例子和上一个很像,只是我们使用了一个自定义属性。
浏览器将 --text-color
的值替换 var(--text-color)
,但 16px
对于 color
来说不是一个有效的属性值。替换后,该属性没有意义,所以浏览器分两步处理这种情况:
- 检查
color
属性是否可继承。它是可继承的,但是这个<p>
没有任何父元素设置了color
属性。所以我们进入下一步。 - 将值设置为其默认初始值,即黑色。
<p>This paragraph is initially black.</p>
:root {
--text-color: 16px;
}
p {
font-weight: bold;
color: blue;
}
p {
color: var(--text-color);
}
对于这种情况,@property
@ 规则可以通过允许定义属性的初始值来防止意外结果。
<p>This paragraph is initially black.</p>
@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 中使用自定义属性的值,就像使用标准属性一样。
// 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);