级联、特异性与继承
本课的目的是帮助你理解 CSS 中一些最基本的概念——级联、特异性和继承——它们控制 CSS 如何应用于 HTML 以及如何解决样式声明之间的冲突。
虽然学习本课的内容在短期内可能看起来不太相关,而且比课程的其他一些部分更具学术性,但理解这些概念可以让你避免以后遇到很多麻烦!我们建议你仔细学习本节内容,并在继续学习之前确保你理解了这些概念。
冲突规则
CSS 代表 **层叠样式表**,而第一个词 *层叠* 非常重要——级联的行为方式是理解 CSS 的关键。
在某些时候,你将在项目中发现你认为应该应用于某个元素的 CSS 无法正常工作。通常,问题在于你创建了两个规则,它们将同一个属性的不同值应用于同一个元素。 **级联** 和与其密切相关的 **特异性** 概念是控制在发生冲突时应用哪个规则的机制。正在为你的元素设置样式的规则可能不是你期望的规则,因此你需要了解这些机制是如何工作的。
同样重要的是 **继承** 的概念,这意味着某些 CSS 属性默认情况下会继承在当前元素的父元素上设置的值,而有些则不会。这也会导致一些你可能意想不到的行为。
让我们首先快速了解一下我们要处理的关键内容,然后我们将依次查看每个内容,并了解它们如何相互作用以及与你的 CSS 如何交互。这些概念可能看起来很难理解。随着你获得更多编写 CSS 的实践经验,它的工作原理会变得更加清晰。
级联
样式表 **级联**——从最简单的层面上来说,这意味着 CSS 规则的来源、级联层和顺序都很重要。当来自同一级联层的两条规则都适用并且特异性相同的时候,样式表中最后定义的那条规则将会被使用。
在下面的示例中,我们有两个规则可以应用于 <h1>
元素。<h1>
内容最终会变成蓝色。这是因为这两个规则都来自同一个来源,具有相同的元素选择器,因此具有相同特异性,但是源代码顺序中最后出现的规则会胜出。
特异性
**特异性**(Specificity)是浏览器用来决定哪个属性值应用于元素的算法。如果多个样式块具有不同的选择器,这些选择器使用不同的值配置相同的属性并定位相同的元素,则特异性将决定应用于该元素的属性值。特异性基本上是衡量选择器选择范围有多具体的指标。
- 元素选择器特异性较低;它将选择页面上出现的所有该类型元素,因此权重较低。伪元素选择器与普通元素选择器具有相同特异性。
- 类选择器特异性较高;它只会选择页面上具有特定
class
属性值的元素,因此权重较高。属性选择器和伪类与类具有相同的权重。
下面,我们再次有两个规则可能应用于<h1>
元素。下面的<h1>
内容最终显示为红色,因为类选择器main-heading
使其规则具有更高的特异性。因此,即使使用<h1>
元素选择器的规则在源顺序中出现得更靠后,但使用类选择器定义的特异性更高的规则将被应用。
稍后我们将解释特异性算法。
继承
在这种情况下,还需要了解继承——父元素上设置的一些 CSS 属性值会被其子元素继承,而有些则不会。
例如,如果您在某个元素上设置了color
和font-family
,则其中的每个元素也将使用该颜色和字体进行样式设置,除非您已直接向它们应用了不同的颜色和字体值。
某些属性不会继承——例如,如果您在某个元素上设置了width
为 50%,则其所有后代都不会获得其父级宽度 50% 的宽度。如果真是这样,CSS 将非常令人沮丧!
注意:在 MDN CSS 属性参考页面上,您可以找到一个名为“正式定义”的技术信息框,其中列出了有关该属性的一些数据点,包括它是否被继承。例如,请参阅color 属性的正式定义部分。
了解这些概念如何协同工作
这三个概念(级联、特异性和继承)共同控制哪些 CSS 应用于哪个元素。在下面的部分中,我们将了解它们如何协同工作。有时这看起来可能有点复杂,但随着您对 CSS 越来越熟悉,您会开始记住它们,如果您忘记了,也可以随时查看详细信息!即使是经验丰富的开发人员也记不住所有细节。
以下视频展示了如何使用 Firefox DevTools 检查页面的级联、特异性等
了解继承
我们将从继承开始。在下面的示例中,我们有一个<ul>
元素,其中嵌套了两级无序列表。我们为外部<ul>
设置了边框、填充和字体颜色。
color
属性是一个继承属性。因此,color
属性值将应用于直接子元素以及间接子元素——直接子元素<li>
以及第一个嵌套列表中的子元素。然后,我们将类special
添加到第二个嵌套列表中,并为其应用了不同的颜色。然后,它将继承到其子元素中。
诸如width
(如前所述)、margin
、padding
和border
之类的属性不是继承属性。如果边框要由此列表示例中的子元素继承,则每个列表和列表项都将获得一个边框——这可能不是我们想要的任何效果!
尽管每个 CSS 属性页面都列出了该属性是否被继承,但如果您知道属性值将设置哪个方面,通常可以凭直觉猜测出相同的结果。
控制继承
CSS 提供了五个用于控制继承的特殊通用属性值。每个 CSS 属性都接受这些值。
inherit
-
将应用于所选元素的属性值设置为与其父元素相同。实际上,这“开启了继承”。
initial
-
将应用于所选元素的属性值设置为该属性的初始值。
revert
-
将应用于所选元素的属性值重置为浏览器的默认样式,而不是应用于该属性的默认值。在许多情况下,此值的作用类似于
unset
。 revert-layer
-
将应用于所选元素的属性值重置为先前级联层中建立的值。
unset
-
将属性重置为其自然值,这意味着如果属性自然继承,则其行为类似于
inherit
,否则其行为类似于initial
。
注意:有关这些内容及其工作原理的更多信息,请参阅源类型。
我们可以查看一个链接列表,并探索通用值的工作原理。下面的实时示例允许您使用 CSS 并查看更改时会发生什么。玩弄代码确实是更好地理解 HTML 和 CSS 的最佳方法。
例如
- 第二个列表项应用了类
my-class-1
。这将嵌套在其中的<a>
元素的颜色设置为inherit
。如果您删除该规则,它会如何更改链接的颜色? - 您是否理解第三和第四个链接的颜色原因?第三个链接设置为
initial
,这意味着它使用属性的初始值(在本例中为黑色),而不是链接的浏览器默认值(蓝色)。第四个链接设置为unset
,这意味着链接文本使用父元素的颜色,绿色。 - 如果您为
<a>
元素定义了一种新颜色(例如a { color: red; }
),哪些链接的颜色会更改? - 在阅读下一节关于重置所有属性的内容后,返回并将其
color
属性更改为all
。请注意第二个链接在新的一行上并带有一个项目符号。您认为哪些属性被继承了?
重置所有属性值
CSS 简写属性all
可用于将这些继承值中的一个一次应用于(几乎)所有属性。其值可以是任何一个继承值(inherit
、initial
、revert
、revert-layer
或unset
)。这是一种方便的方法,可以撤消对样式所做的更改,以便在开始新的更改之前恢复到已知的起点。
在下面的示例中,我们有两个块引用。第一个已将样式应用于块引用元素本身。第二个已将类应用于块引用,该类将all
的值设置为unset
。
尝试将all
的值设置为其他一些可用值,并观察差异。
了解级联
我们现在了解了为什么嵌套在 HTML 结构深处的段落与应用于 body 的 CSS 颜色相同,这是因为继承的原因。从入门课程中,我们了解了如何在文档中的任何位置更改应用于某些内容的 CSS——无论是通过将 CSS 分配给元素还是通过创建类。现在,我们将了解级联如何在多个样式块将相同属性但具有不同值应用于同一元素时定义哪些 CSS 规则适用。
需要考虑三个因素,此处按重要性递增的顺序列出。后面的因素会覆盖前面的因素
- 源顺序
- 特异性
- 重要性
我们将研究这些内容,以了解浏览器如何准确确定应应用哪些 CSS。
源顺序
我们已经了解了源顺序对级联的重要性。如果您有多个规则,所有规则都具有完全相同的权重,则 CSS 中最后出现的规则将获胜。您可以将其视为:越靠近元素本身的规则会覆盖之前的规则,直到最后一个规则获胜并能够为元素设置样式。
只有当规则的特异性权重相同时,源顺序才重要,因此让我们看看特异性。
特异性
您经常会遇到这种情况:您知道某个规则在样式表中出现较晚,但会应用较早的冲突规则。发生这种情况是因为较早的规则具有**更高的特异性**——它更具体,因此浏览器会将其选为应为元素设置样式的规则。
正如我们在本课前面看到的,类选择器比元素选择器权重更大,因此类样式块中定义的属性将覆盖元素样式块中定义的属性。
这里需要注意的是,虽然我们正在考虑选择器以及应用于其选择的文本或组件的规则,但并非整个规则都被覆盖,只有在多个位置声明的属性才会被覆盖。
此行为有助于避免 CSS 中的重复。一种常见的做法是为基本元素定义通用样式,然后为不同的元素创建类。例如,在下面的样式表中,我们为 2 级标题定义了通用样式,然后创建了一些仅更改某些属性和值的类。最初定义的值将应用于所有标题,然后更具体的值将应用于具有这些类的标题。
现在让我们看看浏览器将如何计算特异性。我们已经知道元素选择器特异性低,可以被类覆盖。本质上,会为不同类型的选择器分配一个分数,将这些分数加起来即可得到该特定选择器的权重,然后可以将其与其他潜在匹配进行评估。
选择器具有的特异性使用三个不同的值(或组件)来衡量,可以将其视为 ID、CLASS 和 ELEMENT 列,分别位于百位、十位和个位。
- **标识符**:对于整体选择器中包含的每个 ID 选择器,在此列中得分 1。
- **类**:对于整体选择器中包含的每个类选择器、属性选择器或伪类,在此列中得分 1。
- **元素**:对于整体选择器中包含的每个元素选择器或伪元素,在此列中得分 1。
否定(:not()
)、关系选择器(:has()
)、匹配任何(:is()
)伪类和CSS 嵌套本身不会增加特异性,但其参数或嵌套规则会增加特异性。每个贡献给特异性算法的特异性权重是参数或嵌套规则中权重最大的选择器的特异性权重。
下表显示了一些孤立的示例,让您进入状态。尝试浏览这些示例,并确保您理解为什么它们具有我们给出的特异性。我们还没有详细介绍选择器,但您可以在 MDN 的选择器参考中找到每个选择器的详细信息。
选择器 | 标识符 | 类 | 元素 | 总特异性 |
---|---|---|---|---|
h1 |
0 | 0 | 1 | 0-0-1 |
h1 + p::first-letter |
0 | 0 | 3 | 0-0-3 |
li > a[href*="en-US"] > .inline-warning |
0 | 2 | 2 | 0-2-2 |
#identifier |
1 | 0 | 0 | 1-0-0 |
button:not(#mainBtn, .cta ) |
1 | 0 | 1 | 1-0-1 |
在我们继续之前,让我们看看一个实际示例。
这里发生了什么?首先,我们只对本示例的前七条规则感兴趣,并且您会注意到,我们在每个规则之前都包含了其特异性值作为注释。
- 前两个选择器正在争夺链接背景颜色的样式。第二个选择器获胜并使背景颜色变为蓝色,因为它在链中包含一个额外的 ID 选择器:其特异性为 2-0-1 对 1-0-1。
- 选择器 3 和 4 正在争夺链接文本颜色的样式。第二个选择器获胜并使文本颜色变为白色,因为虽然它少了一个元素选择器,但缺少的选择器被一个类选择器替换,类选择器比无限个元素选择器权重更大。获胜的特异性为 1-1-3 对 1-0-4。
- 选择器 5-7 正在争夺链接悬停时的边框样式。选择器 6 显然输给了选择器 5,特异性为 0-2-3 对 0-2-4;它在链中少了一个元素选择器。但是,选择器 7 击败了选择器 5 和 6,因为它在链中与选择器 5 具有相同数量的子选择器,但一个元素已被一个类选择器替换。因此,获胜的特异性为 0-3-3 对 0-2-3 和 0-2-4。
注意:每种选择器类型都有其自己的特异性级别,无法被特异性级别较低的选择器覆盖。例如,一百万个类选择器组合无法覆盖一个id选择器的特异性。
评估特异性的最佳方法是从最高开始逐个对特异性级别进行评分,并在必要时转向最低级别。只有当选择器分数在特异性列中出现平局时,您才需要评估下一列;否则,您可以忽略特异性较低的选择器,因为它们永远无法覆盖特异性较高的选择器。
内联样式
内联样式,即style
属性内的样式声明,优先于所有普通样式,无论特异性如何。此类声明没有选择器,但其特异性可以理解为 1-0-0-0;始终高于任何其他特异性权重,无论选择器中有多少个 ID。
!important
CSS 中有一个特殊的片段,您可以使用它来覆盖上述所有计算,甚至包括内联样式——!important
标记。但是,在使用它时应格外小心。此标记用于使单个属性和值对成为最具体的规则,从而覆盖级联的正常规则,包括正常的内联样式。
注意:了解!important
标记的存在非常有用,这样您在其他人的代码中遇到它时就知道是什么了。但是,我们强烈建议您除非绝对必要,否则不要使用它。!important
标记会更改级联的正常工作方式,因此它可能使调试 CSS 问题变得非常困难,尤其是在大型样式表中。
让我们看一下此示例,其中有两个段落,其中一个具有 ID。
让我们逐步了解这里发生了什么——如果您发现难以理解,请尝试删除某些属性以查看会发生什么。
- 您会看到,第三条规则的
color
和padding
值已被应用,但background-color
尚未应用。为什么?实际上,所有三个规则都应该应用,因为源顺序中较后的规则通常会覆盖较早的规则。 - 但是,上面的规则获胜,因为类选择器比元素选择器特异性更高。
- 两个元素都具有
better
的class
,但第二个元素也具有winning
的id
。由于 ID 的特异性比类更高(您在一个页面上只能有一个具有唯一 ID 的元素,但可以有多个具有相同类的元素——ID 选择器在其目标方面非常具体),因此红色背景颜色和 1px 黑色边框都应应用于第二个元素,第一个元素将获得灰色背景颜色,并且没有边框,如类中指定的那样。 - 第二个元素确实获得了红色背景颜色,但没有边框。为什么?因为第二个规则中使用了
!important
标记。在border: none
之后添加!important
标记意味着此声明将胜过前一个规则中的border
值,即使ID选择器的特异性更高。
注意:覆盖重要声明的唯一方法是在源代码顺序中稍后包含另一个具有相同特异性的重要声明,或具有更高特异性的声明,或在先前的级联层中包含重要声明(我们还没有介绍级联层)。
在某些情况下,您可能需要使用!important
标记,例如当您在无法编辑核心CSS模块的CMS上工作时,并且您确实想要覆盖内联样式或无法以其他方式覆盖的重要声明。但是,如果可以避免,请尽量不要使用它。
CSS 位置的影响
最后,务必注意,CSS声明的优先级取决于它在哪个样式表和级联层中指定。
用户可以设置自定义样式表来覆盖开发人员的样式。例如,视力障碍的用户可能希望将所有访问网页的字体大小设置为正常大小的两倍,以便更容易阅读。
也可以在级联层中声明开发人员样式:您可以使非分层样式覆盖在层中声明的样式,或者使在后续层中声明的样式覆盖先前声明的层的样式。例如,作为开发人员,您可能无法编辑第三方样式表,但您可以将外部样式表导入到级联层中,以便您的所有样式轻松覆盖导入的样式,而无需担心第三方选择器的特异性。
覆盖声明的顺序
冲突的声明将按以下顺序应用,后面的声明将覆盖前面的声明
- 用户代理样式表中的声明(例如,浏览器默认样式,在没有其他样式设置时使用)。
- 用户样式表中的普通声明(用户设置的自定义样式)。
- 作者样式表中的普通声明(这些是我们,网页开发人员设置的样式)。
- 作者样式表中的重要声明。
- 用户样式表中的重要声明。
- 用户代理样式表中的重要声明。
注意:对于使用!important
标记的样式,优先级顺序是相反的。网页开发人员的样式表覆盖用户样式表是有意义的,这样可以保持设计意图;但是,有时用户有充分的理由覆盖网页开发人员的样式,如上所述,这可以通过在其规则中使用!important
来实现。
级联层的顺序
即使级联层是一个高级主题,您可能不会立即使用此功能,但了解层如何级联非常重要。
当您在级联层中声明CSS时,优先级顺序由声明层的顺序决定。在任何层之外声明的CSS样式将组合在一起,按照这些样式声明的顺序,组合到一个未命名的层中,就像它是最后一个声明的层一样。对于竞争的普通(非重要)样式,后面的层优先于前面定义的层。但是,对于使用!important
标记的样式,顺序将反转,前面层中的重要样式优先于后续层或任何层之外声明的重要样式。内联样式优先于所有作者样式,无论层如何。
当您在不同层中的多个样式块为单个元素上的属性提供冲突的值时,声明层的顺序决定优先级。层之间的特异性无关紧要,但单个层内的特异性仍然重要。
让我们从上面的示例中讨论一些内容,以了解发生了什么。已按顺序声明了两个层,firstLayer
和secondLayer
。即使secondLayer
中的特异性最高,也不会使用该声明中的任何属性。为什么?因为非分层普通样式优先于分层普通样式,无论特异性如何,并且重要分层样式优先于在后续层中声明的重要样式,同样,无论特异性如何。
如果将此示例中的第一行CSS更改为@layer secondLayer, firstLayer;
,您将更改层声明顺序,并且firstLayer
中的所有重要样式将更改为其在secondLayer
中的相应值。
作用域邻近性
另一个您可能不会立即使用但将来可能需要了解的高级主题是@scope
。这是一个at规则,使您能够创建仅适用于页面上特定HTML子部分的一组规则。例如,您可以指定仅适用于<img>
元素的样式,当它们嵌套在具有feature
类的元素内部时。
@scope (.feature) {
img {
border: 2px solid black;
display: block;
}
}
作用域邻近性是解决作用域元素之间冲突的机制。作用域邻近性指出,当两个作用域具有冲突的样式时,DOM树层次结构向上到作用域根的跳跃次数最少的样式获胜。有关更多详细信息和示例,请参阅如何解决@scope
冲突。
测试你的技能!
您已阅读完本文,但您还记得最重要的信息吗?在继续之前,您可以进行一些进一步的测试来验证您是否保留了这些信息——请参阅测试您的技能:级联。
总结
如果您理解了本文的大部分内容,那么恭喜——您已经开始熟悉CSS的基本机制。接下来,我们将深入了解级联层。
如果您没有完全理解级联、特异性和继承,请不要担心!这绝对是我们到目前为止在课程中涵盖的最复杂的事情,即使是专业的网页开发人员有时也会发现它很棘手。我们建议您在继续学习课程的过程中多次返回本文,并继续思考它。
如果您开始遇到样式未按预期应用的奇怪问题,请参考此处。这可能是一个特异性问题。