CSS 教程 / CSS 继承、选择器优先级与层叠

层叠在竞争的 CSS 声明之间进行选择,选择器优先级(specificity)在相同的相关层叠条件内比较选择器的权重。继承在没有适用声明确定值时从父元素提供某些值;这些是相关但不同的机制。

区分继承与直接声明

继承是指子元素继承其父元素的值。虽然文本颜色和字体相关属性通常会被继承,但边框和内边距默认情况下不会被继承。不能因为父元素有边框,就期待每个子元素也有自己的边框。

#main {
  color: purple;
  border: 1px solid purple;
}
p { color: #555; }
<main id="main">
  <h1>Small Garden</h1>
  <p>Visitor information.</p>
</main>

假设我们只应用这两个部分,h1 将会继承其父元素 main 的紫色。你指定的灰色则应用于 p。仅仅因为父元素的 #main 是一个 ID 选择器,并不意味着它会覆盖子元素 p 的直接赋值。这是因为这不是两个选择器同时作用于同一元素的竞争。边框是围绕 main 的盒子创建的,并不会被 h1 和 p 各自的边框重复。

非继承属性也可以通过指定 border: inherit 来继承父级的计算值。另一方面,即使某个属性默认会继承,只要元素自身有适用声明,也应先处理该声明。你可以在 MDN 的 CSS 继承描述中查找哪些属性是继承的,以及继承的含义。

遵循层叠决策过程

从普通作者 CSS 开始,例如你为此页面编写的规则。在比较声明之前,验证选择器是否匹配,语法是否有效,以及媒体查询等条件是否适用。将不匹配的规则和无效的声明排除在比较之外。

  1. 检查顶层条件,例如来源、重要性和层叠层(cascade layer)。作者 CSS 和用户 CSS/浏览器默认样式的来源、是否使用 !important、是否使用 @layer 等在此都是相关的。如果顶层条件不同,仅比较选择器的分数无法做出决定。
  2. 在相同条件下比较选择器的优先级。选择器优先级衡量选择器的具体程度。按照 ID、类族和元素族的顺序进行比较。
  3. 如果优先级相同,则使用后来写的声明。在没有其他条件(如 @scope)的情况下,如本课中的示例,最终平局按声明顺序解决。
  4. 对于每个属性,CSS 会解析适用的声明,然后在必要时执行默认值处理。可继承的属性可以获取父元素的值;其他属性通常使用它们的初始值。这并不意味着获胜规则中的每个声明都会替换整个先前的规则。

层叠是完整的 CSS 冲突解决过程;选择器优先级是其中的一个标准。动画、过渡、层和作用域引入了更多规则,因此这里的简单示例并不能代表所有情况。W3C 层叠排序规则 定义了来源、重要性和层之间的关系。下面的完整示例未使用动画、过渡、@layer 或 @scope。使用共享样式、嵌套和层组织 CSS 演示了将规则分组到层中。

在三列中比较优先级

本课的基本选择器可以从三列来阅读:ID 数量、类/属性/伪类数量,以及元素/伪元素数量。从最左边的列进行比较,如果有差异,则根据比较结果决定。不要只是简单地把它记成一个十进制分数表,比如 ID=100,类=10,元素=1。仅仅因为有很多类,并不意味着它会超过 ID 列。

选择器 ID 类、属性、伪类 元素/伪元素 标记法
p 0 0 1 0-0-1
.notice 0 1 0 0-1-0
p.notice 0 1 1 0-1-1
[required] 0 1 0 0-1-0
a:hover 0 1 1 0-1-1
p::before 0 0 2 0-0-2
#opening 1 0 0 1-0-0
#main .notice 1 1 0 1-1-0

p.notice 和 .notice 有相同的 ID 列和相同的类列。元素列中 1 与 0 存在差异,所以 p.notice 的优先级更高。#opening 和 p.notice 在第一列中差异为 1 和 0,所以 #opening 的优先级更高。空白、关系符号如 >、+、~ 及通配选择器 * 不会直接增加这些比较的数量。

p.notice { color: #087f5b; }
.notice { color: tomato; }

在相同的来源、重要性和层级条件下,p.notice 的优先级高于 .notice,因此即使它出现在较早的位置,其绿色也会胜出。相同的选择器具有相等的优先级,由声明顺序解决平局。一些功能性伪类具有额外的优先级规则。参见 MDN:选择器优先级。

分别解决每个属性

.notice {
  color: blue;
  padding: 12px;
  border: 1px solid #087f5b;
}
.notice {
  color: tomato;
  padding: 20px;
}

如果仅将这两条规则应用于同一元素,颜色和内边距将使用后者的声明,边框则保持前一条规则的值。仅因为后续规则中没有边框,并不意味着整个前一条规则消失。CSS 应理解为“为每个属性确定值”,而不是“选择整个规则块”。

例如,文本颜色可能来自 ID 规则,边距可能来自后续的类规则,边框可能来自之前的类规则。即使在开发者工具中看到删除线,也要逐一阅读声明。仅因为颜色有删除线,并不意味着同一块的内边距没有被应用。

在运行之前预测完整示例

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Small Garden</title>
  <style>
body { margin: 24px; font-family: sans-serif; line-height: 1.7; }
    #main { color: purple; }
    p { color: #555; }
    .notice {
      color: blue;
      padding: 12px;
      border: 1px solid #087f5b;
    }
    p.notice { color: #087f5b; }
    .notice { color: tomato; padding: 20px; }
    #opening { color: #1d4ed8; }
  </style>
</head>
<body>
<main id="main">
    <h1>Small Garden</h1>
    <p id="opening" class="notice">A: We open at 9am every day.</p>
    <p class="notice">B: Prepare a water bottle and comfortable shoes.</p>
    <p>C: Please inquire at the information desk.</p>
  </main>
</body>
</html>

在浏览器中打开之前,预测 A·B·C 的颜色和提示信息的内边距。如果你将整个代码保存为 index.html 并打开,你将得到以下结果。正文字体和行高是辅助设置,便于查看。

对象和属性 最终结果 决策原因
h1的颜色 紫色 继承 main 元素的文字颜色;h1 本身没有其他颜色声明。
A的颜色 蓝色 #1d4ed8 #opening 的优先级高于 p.notice、.notice、p 应用于同一个 A。
B的颜色 绿色 #087f5b p.notice 的优先级高于后面的 .notice。
C的颜色 灰色 #555 它为 p 使用了直接值,并且不继承父元素的紫色。
A·B 的内边距 20px 这两个 .notice 声明具有相同的优先级,因此后者的内边距生效。
A·B 的边框 1px 绿色实线 由于后一个规则中没有边框声明,因此前一个有效声明仍然有效。

在 Computed(计算值)面板中检查时,A 的颜色可以显示为 rgb(29, 78, 216),B 的颜色可以显示为 rgb(8, 127, 91),C 的颜色可以显示为 rgb(85, 85, 85)。即使颜色表示法不同,也表示相同的值。检查每个方向的内边距为 20px,并且边框厚度和线型相同。

比较 !important 和内联样式

!important 是一种改变声明重要性的标记。这不是一个向选择器的三个列增加大数字的函数。根据同一作者的来源,important 声明优先于普通声明,因此将以下规则添加到整个示例的末尾将把 A 和 B 的颜色都改为 tomato。ID 中的普通颜色声明也不会优先于该 important 声明。

.notice { color: tomato !important; }

这个结果并不意味着重要(important)总是比所有样式都强。用户来源的 important 样式、过渡期间的值等遵循一个单独的顺序。即使在重要声明中,你也必须先检查来源和层。在这个练习中,先观察 !important 的效果,然后删除你添加的规则。通常,为了便于维护,先整理选择范围和结构会更好。

<p class="notice" style="color: purple;">B: Information on what to prepare</p>

在原始示例中,当删除 !important 规则后,如果像上面一样在 B 中设置内联颜色,它将优先于常规样式表规则,B 将变为紫色。内联样式与上表中的三个默认选择器列是分开处理的。同样,在常规类规则中使用 important 可以使其优先于常规的内联颜色。内联样式的优先关系也必须结合声明重要性等条件判断。

练习诊断冲突

  1. 在原始完整示例中,将 p.notice 规则移动到底部。确保 B 仍然是绿色,A 保持其 ID 的蓝色。在高优先级状态下,仅仅改变顺序不会改变它们。
  2. 接下来,只更改 p.notice 选择器为 .notice,并将其放到底部。B 的颜色使用最后一个 .notice 指定的绿色。由于我们有相同的选择器,顺序现在成为决定标准。确认后,恢复原状。
  3. 只从 A 中移除 id="opening"。看看 A 是否变成与 B 相同的绿色。尽管父元素 main 的 ID 保持不变,但它无法覆盖直接分配给 A 的绿色。确认后,恢复原来的 ID。
  4. 在单独步骤中,逐一应用上述重要性和内联示例并进行比较。每次先恢复原始代码,再测试一个条件。

如果结果与您的预期不同,请参考 使用开发者工具检查 CSS:Styles 与 Computed。选择正确的元素,找到该属性的声明,比较它们的重要性、选择器和顺序,然后检查最终值。未找到原因而添加 ID 或 !important 可能会立即更改颜色,但会使以后的编辑更加困难。