CSS 教程 / CSS 变量:var()、作用域、继承与回退值
CSS 自定义属性保存可复用值, 并在属性值中通过 var() 引用。它们的作用域由层叠与继承决定, 并且 var() 的备用值不会自动替换已经定义、但对目标属性无效的值。
选择有意义的共享变量
如果你为多个按钮和卡片使用相同的强调色,你可以将其分组到一个有意义的变量中,而不是将颜色代码复制到每个选择器中。修改强调色时,如果你更改声明,引用该值的地方也会随之更改。如果你创建变量,即使是只在一个地方使用且没有独立作用的值,跟踪名称的负担实际上可能会增加。
:root {
--accent: #1d5f91;
--space-unit: .5rem;
}
.button { color: white; background: var(--accent); }
.card { border: 2px solid var(--accent); padding: calc(var(--space-unit) * 3); }
仅在上面的代码中声明一个颜色并不会改变屏幕。需要在 background 或 border 等实际属性的值中使用 var() 引用变量。如果名称表示角色,例如 --accent,而不是固定当前颜色,例如 --blue,那么在将来更改为不同颜色时会更不混淆。--accent 和 --Accent 是不同的名称,因此要匹配大小写和拼写。
var() 用于 color 或 padding 等属性的值。变量不能替换选择器或属性名称,也不能把 var() 直接放进 @media (min-width: ...) 来读取普通自定义属性作为断点。容器尺寸查询有独立的条件语法,应按具体规范与目标浏览器支持判断,不能把普通属性值的替换规则推广到所有条件查询。本课将变量用于颜色与间距等属性值。
理解根、继承和组件作用域
在 HTML 文档中,:root 匹配顶级 HTML 元素,因此在文档中有一个通用的默认值是很方便的。本课未注册的 -- 自定义属性默认由后代继承。通过 @property 注册时,可以声明类型、初始值,并用 inherits: false 关闭继承;本课示例未使用注册。卡片中声明的值可以被子元素使用,例如标题,但不会传递给无关的同级卡片。
.card { --card-accent: var(--accent); }
.card.warning { --card-accent: #a63e32; }
.card { border-color: var(--card-accent); }
.card h2 { color: var(--card-accent); }
此规则是下面完整示例的一个组成部分。.card.warning 匹配同时具有两个类的卡片。胜负不是由 HTML 类的列出顺序决定的,而是通过匹配 CSS 继承、特异性和级联 来确定值。更改全局强调色将更改默认卡片,而具有自身卡片强调色的警告卡片将保留其原始警告颜色。
在将变量与其他变量结合使用时,值是从哪个元素计算的也很重要。如果 --line-color: var(--accent) 在父元素中计算,然后传递给子元素,那么已经继承的 --line-color 值不会仅仅因为子元素中的强调色被覆盖而重新计算。如果派生值需要跟随子元素自身的 --accent 变化,应在该元素上重新声明派生变量,或在实际属性中直接引用 --accent。有关作用域和继承的定义,请参见 CSS 自定义属性规范。
区分回退值和无效的计算值
var(--notice-color, #6a3989) 在本例变量未定义或其本身为无效值时提供一个紫色的备用值。变量即使已经定义,例如值为 20px,也不代表它适用于 color;长度可以用于某些尺寸属性,但不是有效颜色,替换后会让颜色声明在计算时无效。在这种情况下,你不应该期望它会“被替换为紫色”。
| 变量状态 | 颜色声明的结果 | 整个示例的颜色 |
|---|---|---|
| 未定义 --notice-color | 为 var 使用备用值 | 紫色 |
| --notice-color 是红色 | 使用已定义的值 | 红色 |
| --notice-color 是 20px | 由于颜色不匹配,因此应用了继承/初始值规则 | 因为颜色可继承,使用父元素的绿色文字颜色。 |
如下所示,即使在同一个规则中首先声明了蓝色,如果在第二个声明进行变量替换后的值不匹配,也不会回到第一个蓝色。当在级联中选择的声明在评估时无效时,将应用颜色的继承规则。在同样的情况下,非继承的属性可能被视为该属性的初始值。MDN 自定义属性指南 解释了替换值和无效值之间的区别。
.sample {
color: #1d5f91;
color: var(--notice-color, #6a3989);
}
.sample.bad { --notice-color: 20px; }
你也可以在回退值中放入其他变量。例如,var(--button-accent, var(--accent, #1d5f91)) 会查找要使用的值,先从按钮特定值开始,然后是通用值,最后是主颜色。然而,如果数字、长度和颜色的使用混合在一起,就会出现同样的问题,因此变量名称和使用必须一起管理。
使用 calc() 结合值和单位
示例将 --space-unit: .5rem 乘以 3 来设置卡片内边距。rem 使用根字体大小,因此更改该大小会改变间距。使用 calc() 进行计算,而不是连接字符串,并且在加法和减法运算符周围留空格。更多详细信息,请参阅 CSS 颜色和单位:px、em、rem 和百分比。
:root {
--space-unit: .5rem;
--space-card: calc(var(--space-unit) * 3);
}
.card { padding: var(--space-card); }
始终一致地选择变量是带单位还是仅数字。如果在长度变量中输入无单位值(例如 2),它可能不适用于需要单位的应用程序。避免不同用途的组合,例如将 calc 应用于包含颜色的变量。有关公式的基本语法,请参阅 MDN:calc()。
运行作用域和回退比较
将以下代码保存为 UTF-8 的 custom-properties.html 并在浏览器中打开。比较两张卡片的强调色和三句话的文本颜色。示例同时用文字说明各项颜色的含义,让读者无需只凭颜色区分结果。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CSS variable scope and substitution values</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; padding: 1rem; color: #183044; font-family: sans-serif; line-height: 1.6; }
main { max-width: 46rem; margin-inline: auto; }
h1 { font-size: 1.5rem; } h2 { font-size: 1.2rem; }
button, input { font: inherit; }
button { cursor: pointer; }
:focus-visible { outline: 3px solid #164f73; outline-offset: 3px; }
:root { --space-unit: .5rem; --space-card: calc(var(--space-unit) * 3); --accent: #1d5f91; }
.cards { display: grid; gap: 1rem; }
.card { --card-accent: var(--accent); padding: var(--space-card); border: 2px solid var(--card-accent);
border-radius: .5rem; background: #f6f9fb; }
.card h2 { color: var(--card-accent); margin-block: 0 .5rem; }
.card p { margin: 0; }
.card.warning { --card-accent: #a63e32; }
.fallback-area { margin-block: 2rem; padding: 1rem; border: 1px solid #8a9ba7; color: #28633b; }
.sample { color: #1d5f91; color: var(--notice-color, #6a3989); }
.sample.bad { --notice-color: 20px; }
.sample.good { --notice-color: #a63e32; }
</style>
</head>
<body>
<main>
<h1>Variable scope and fallbacks</h1>
<div class="cards">
<article class="card"><h2>Basic card</h2><p>The heading and border share the same accent color.</p></article>
<article class="card warning"><h2>warning card</h2><p>The accent color changes only inside this card.</p></article>
</div>
<section class="fallback-area">
<h2>The parent's text color is green</h2>
<p class="sample missing">Undefined: purple fallback</p>
<p class="sample bad">20px: Doesn't match the color, so it inherits the green color from the parent.</p>
<p class="sample good">Defined as red: use that color</p>
</section>
</main>
</body>
</html>
- 确保基本卡片的标题和边框为蓝色,警告卡片为红色。下面的三句话分别为紫色、绿色和红色。
- 仅将根强调色更改为
#28633b。默认卡片的标题和边框应更改,而警告卡片保持其警告颜色。检查后将根强调色恢复为#1d5f91。 - 仅将 :root 中的 --space-unit 改为
1rem。两张卡片的内边距会一起拉伸。确认后恢复为 .5rem。 - 仅将 .bad 的 --notice-color 从
20px改为#1d5f91。句子将从绿色变为蓝色。确认后将其恢复为 20px。 - 仅删除 .bad 的 --notice-color 声明。这次使用了紫色回退。与上一步的蓝色结果进行比较,并恢复原始声明。
诊断缺失或错误的变量
- 在开发者工具中,检查变量名和值是否实际存在于该元素中。首先查看拼写、大小写和声明范围。
- 检查后代元素或其他规则是否覆盖了相同的变量。即使公共值发生变化,组件保留其独特值也可能不是错误。
- 检查该变量是否在实际属性中被引用,以及替代值的单位和类型是否正确。
- 如果一个值是通过多个变量创建的,请检查计算出的元素和继承的值。还要避免声明引用自身的相同变量或循环引用的变量。
你可以通过区分设计中的常用颜色和间距以及每个组件的例外情况来利用变量,并在更改前后检查结果。与其增加变量名称的数量,不如明确哪个范围共享哪些值,这对维护更有帮助。




