处理冲突

本节课旨在加深你对 CSS 中一些最基本概念的理解——层叠、优先级和继承——它们控制着 CSS 如何应用于 HTML 以及如何解决样式声明之间的冲突。

虽然学习本节课内容可能不像课程的其他部分那样能立竿见影,甚至会感觉有点学术化,但理解这些概念将在以后为你免去很多麻烦!我们鼓励你仔细学习本节内容,并在继续学习前确保自己已理解这些概念。

预备知识 HTML 基础(学习基本的 HTML 语法),CSS 选择器
学习成果
  • 了解 CSS 中的规则如何产生冲突。
  • 继承。
  • 层叠。
  • 决定冲突结果的主要概念——优先级、源顺序和重要性。

规则冲突

CSS 是“层叠样式表”(Cascading Style Sheets)的缩写,第一个词“层叠”(cascading)非常重要,需要深刻理解——层叠的运作方式是理解 CSS 的关键。

在某些时候,你会在项目中发现,你认为应该应用于某个元素的 CSS 并没有生效。通常,这个问题发生在你创建了两条规则,将同一属性的不同值应用于同一元素时。

层叠Cascade)以及与之密切相关的优先级specificity)概念,是控制在发生此类冲突时应用哪条规则的机制。为元素设置样式的声明可能不是你期望的那一个,所以你需要了解这些机制是如何工作的。

在这里,继承inheritance)的概念也很重要,它指的是一些 CSS 属性会默认继承当前元素父元素上设置的值,而另一些则不会。这也可能导致意想不到的行为。

让我们先快速了解一下我们将要处理的关键概念,然后逐一审视,看看它们如何相互作用以及如何影响你的 CSS。这些概念看起来可能难以理解,但随着你编写 CSS 的经验越来越丰富,它们会变得越来越清晰。

层叠

样式表是层叠的。简单来说,这意味着 CSS 规则的来源和顺序很重要。当两条规则的优先级相同时,样式表中最后定义的那条规则将被使用。还有其他一些概念也会产生影响,比如层叠层,但这些内容更高级,我们在这里不作详细介绍。

在下面的例子中,我们有两条规则可以应用于 <h1> 元素。<h1> 的内容最终变成了蓝色。这是因为这两条规则都来自同一个来源,拥有相同的元素选择器,因此具有相同的优先级,但源顺序中最后的那条规则获胜。

html
<h1>This is my heading.</h1>
css
h1 {
  color: red;
}
h1 {
  color: blue;
}

优先级

优先级是浏览器用来决定哪个属性值应用于元素的算法。如果多条规则有不同的选择器,它们为同一元素设置了同一属性的不同值,那么优先级将决定哪个属性值被应用到该元素上。优先级基本上是衡量一个选择器的选择范围有多具体的标准。

  • 类型(元素)选择器的优先级较低;它会选中页面上出现的所有该类型的元素,所以它的权重较低。伪元素选择器与普通元素选择器具有相同的优先级。
  • 类选择器的优先级更高;它只会选中页面上具有特定 class 属性值的元素,所以它的权重更高。属性选择器和伪类与类选择器具有相同的权重。
  • ID 选择器的优先级甚至更高——它只选中一个具有特定 id 值的元素。因此,它的权重更大。

下面,我们又有两条可以应用于 <h1> 元素的规则。<h1> 的内容最终变成了红色,尽管 color: blue 声明在源顺序中出现得更晚,这是因为类选择器 main-heading 使其规则的优先级高于类型选择器 h1。由类选择器定义的、优先级更高的声明被应用了。

html
<h1 class="main-heading">This is my heading.</h1>
css
.main-heading {
  color: red;
}

h1 {
  color: blue;
}

我们稍后会解释优先级的计算算法。

继承

在这个背景下,继承也需要被理解——一些设置在父元素上的 CSS 属性值会被其子元素继承,而另一些则不会。

例如,如果你为一个元素设置了 colorfont-family,那么它内部的每个元素也会被设置为该颜色和字体,除非你直接为它们应用了不同的颜色和字体值。

html
<p>
  As the body has been set to have a color of blue this is inherited through the
  descendants.
</p>
<p>
  We can change the color by specifically targeting an element with a different
  style, such as this
  <span>span</span>.
</p>
css
body {
  color: blue;
}

span {
  color: black;
}

有些属性不会被继承——例如width。如果你为一个元素设置了 width50%,它的所有后代元素不会都获得其父元素 width50%。如果真是这样,使用 CSS 将会非常令人沮丧!

备注: 在 MDN 的 CSS 属性参考页面上,你可以找到一个名为“正式定义”的技术信息框,其中列出了关于该属性的许多数据点,包括它是否被继承。例如,可以查看 color 属性的“正式定义”部分

理解这些概念如何协同工作

这三个概念(层叠、优先级和继承)共同控制着哪条 CSS 应用于哪个元素。在下面的章节中,我们将看到它们如何协同工作。这有时可能看起来有点复杂,但随着你对 CSS 越来越有经验,你会开始记住它们,如果忘记了也可以随时查阅细节!即使是经验丰富的开发者也记不住所有的细节。

理解继承

我们从继承开始。在下面的例子中,我们有一个 <ul> 元素,里面嵌套了两层无序列表。我们为外部的 <ul> 设置了边框、内边距和字体颜色。

color 属性是一个可继承的属性。因此,color 属性值被应用到直接子元素和间接子元素上——即直接的 <li> 子元素和第一个嵌套列表中的那些。然后,我们为第二个嵌套列表添加了 special 类,并为其应用了不同的颜色。这个颜色随后会向下继承给它的子元素。

html
<ul class="main">
  <li>Item One</li>
  <li>
    Item Two
    <ul>
      <li>2.1</li>
      <li>2.2</li>
    </ul>
  </li>
  <li>
    Item Three
    <ul class="special">
      <li>
        3.1
        <ul>
          <li>3.1.1</li>
          <li>3.1.2</li>
        </ul>
      </li>
      <li>3.2</li>
    </ul>
  </li>
</ul>
css
.main {
  color: rebeccapurple;
  border: 2px solid #cccccc;
  padding: 1em;
}

.special {
  color: black;
  font-weight: bold;
}

像前面提到的 widthmarginpaddingborder 等属性是不可继承的。如果在这个列表例子中边框被子元素继承,那么每个列表和列表项都会有一个边框——这可能不是我们想要的效果!

虽然每个 CSS 属性页面都列出了该属性是否可继承,但如果你知道该属性值会影响哪个方面,通常可以凭直觉猜出答案。

控制继承

CSS 提供了五个特殊的通用属性值来控制继承。每个 CSS 属性都接受这些值。

inherit

将应用于所选元素的属性值设置为与其父元素相同。实际上,这“开启了继承”。

initial

将应用于所选元素的属性值设置为该属性的初始值

revert

将应用于所选元素的属性值重置为浏览器的默认样式,而不是应用于该属性的默认值。在许多情况下,这个值的行为类似于 unset

revert-layer

将应用于所选元素的属性值重置为前一个层叠层中确立的值。

unset

将属性重置为其自然值,这意味着如果该属性本身是可继承的,它的行为就像 inherit,否则就像 initial

备注: 有关这些值以及它们如何工作的更多信息,请参见来源类型

体验继承控制属性

我们可以通过一个链接列表来探索通用值是如何工作的。下面的实时示例允许你修改 CSS 并观察当你做出改变时会发生什么。实际操作代码是更好地理解 HTML 和 CSS 的最佳方式。

例如

  1. 第二个列表项应用了 my-class-1 类。这将其内部嵌套的 <a> 元素的颜色设置为 inherit。如果你移除这条规则,链接的颜色会如何变化?
  2. 你理解为什么第三和第四个链接是它们现在的颜色吗?第三个链接被设置为 initial,这意味着它使用了属性的初始值(在这种情况下是黑色),而不是浏览器为链接设置的默认值蓝色。第四个被设置为 unset,这意味着链接文本使用了其父元素的颜色,即绿色。
  3. 如果你为 <a> 元素定义一个新的颜色——例如 a { color: red; }——哪些链接的颜色会改变?
  4. 在阅读下一节关于重置所有属性的内容后,回来将 color 属性改为 all。注意第二个链接是如何换行并带有一个项目符号的。你认为哪些属性被继承了?
html
<ul>
  <li>Default <a href="#">link</a> color</li>
  <li class="my-class-1">Inherit the <a href="#">link</a> color</li>
  <li class="my-class-2">Reset the <a href="#">link</a> color</li>
  <li class="my-class-3">Unset the <a href="#">link</a> color</li>
</ul>
css
body {
  color: green;
}

.my-class-1 a {
  color: inherit;
}

.my-class-2 a {
  color: initial;
}

.my-class-3 a {
  color: unset;
}

重置所有属性值

CSS 的简写属性 all 可以用来将这些继承值之一一次性应用于(几乎)所有属性。它的值可以是任何一个继承值(inheritinitialrevertrevert-layerunset)。这是一种便捷的方式,可以撤销对样式所做的更改,以便在开始新的更改之前回到一个已知的起点。

在下面的例子中,我们有两个 blockquote。第一个为 blockquote 元素本身应用了样式。第二个为 blockquote 应用了一个类,该类将 all 的值设置为 unset

html
<blockquote>
  <p>This blockquote is styled</p>
</blockquote>

<blockquote class="fix-this">
  <p>This blockquote is not styled</p>
</blockquote>
css
blockquote {
  background-color: orange;
  border: 2px solid blue;
}

.fix-this {
  all: unset;
}

尝试将 all 的值设置为其他一些可用值,并观察有什么不同。

理解层叠

我们现在理解了,继承是为什么深嵌在 HTML 结构中的段落颜色与应用于 body 的 CSS 颜色相同的原因。通过前面的课程,我们已经了解了如何在文档的任何位置更改应用的 CSS——无论是通过为元素分配 CSS 还是通过创建类。现在,我们将探讨当多个样式块对同一元素应用相同属性但值不同时,层叠如何定义哪条 CSS 规则生效。

有三个因素需要考虑,按重要性递增的顺序列出。后面的因素会覆盖前面的

  1. 源顺序
  2. 优先级
  3. 重要性

我们将逐一审视这些因素,看看浏览器究竟是如何确定应该应用哪些 CSS 的。

源顺序

我们已经看到了源顺序对层叠的重要性。如果你有多条规则,它们的权重完全相同,那么 CSS 中最后出现的那条规则将获胜。你可以这样想:离元素本身越近的规则会覆盖前面的规则,直到最后一条规则胜出并为元素设置样式。

源顺序只有在规则的优先级权重相同时才起作用,所以接下来我们看看优先级。

优先级

你常常会遇到这样的情况:你知道一条规则在样式表中出现得更晚,但一条更早的、有冲突的规则却被应用了。这是因为更早的那条规则具有更高的优先级——它更具体,因此被浏览器选择来为元素设置样式。

正如我们在本节课前面看到的,类选择器的权重高于元素选择器,所以类样式块中定义的属性会覆盖元素样式块中定义的属性。

这里需要注意的是,虽然我们考虑的是选择器以及应用于它们所选中的文本或组件的规则,但被覆盖的不是整个规则,而只是在多个地方声明的那些属性。

这种行为有助于避免 CSS 中的重复。一个常见的做法是为基本元素定义通用样式,然后为那些不同的元素创建类。例如,在下面的样式表中,我们为二级标题定义了通用样式,然后创建了一些只改变部分属性和值的类。最初定义的值会应用于所有标题,然后更具体的值会应用于带有这些类的标题。

html
<h2>Heading with no class</h2>
<h2 class="small">Heading with class of small</h2>
<h2 class="bright">Heading with class of bright</h2>
css
h2 {
  font-size: 2em;
  color: black;
  font-family: "Georgia", serif;
}

.small {
  font-size: 1em;
}

.bright {
  color: rebeccapurple;
}

现在让我们看看浏览器是如何计算优先级的。我们已经知道,元素选择器的优先级低,可以被类选择器覆盖。基本上,不同类型的选择器会被赋予一个分值,将这些分值相加就得到了该选择器的权重,然后可以与其他潜在匹配的选择器进行比较。

一个选择器的优先级大小由三个不同的值(或组成部分)来衡量,可以把它们看作是分别代表百、十、个位的 ID、CLASS 和 ELEMENT 列

  • ID:对于整个选择器中包含的每个 ID 选择器,在这一列得一分(100 分)。
  • :对于整个选择器中包含的每个类选择器、属性选择器或伪类,在这一列得一分(10 分)。
  • 元素:对于整个选择器中包含的每个元素选择器或伪元素,在这一列得一分(1 分)。

备注: 通用选择器(*)、组合器+>~、' ')以及优先级调整选择器(:where())及其参数对优先级没有影响。

下表展示了几个独立的例子,让你感受一下。试着过一遍这些例子,确保你理解为什么它们具有我们给出的优先级。我们还没有详细介绍选择器,但你可以在 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

深入优先级示例

在我们继续之前,让我们看一个实际的例子。你可能想在单独的标签页中用 MDN Playground 打开它,以便在阅读解释时可以轻松地对照参考。

html
<div class="container" id="outer">
  <div class="container" id="inner">
    <ul>
      <li class="nav"><a href="#">One</a></li>
      <li class="nav"><a href="#">Two</a></li>
    </ul>
  </div>
</div>
css
/* 1. specificity: 1-0-1 */
#outer a {
  background-color: red;
}

/* 2. specificity: 2-0-1 */
#outer #inner a {
  background-color: blue;
}

/* 3. specificity: 1-0-4 */
#outer div ul li a {
  color: yellow;
}

/* 4. specificity: 1-1-3 */
#outer div ul .nav a {
  color: white;
}

/* 5. specificity: 0-2-4 */
div div li:nth-child(2) a:hover {
  border: 10px solid black;
}

/* 6. specificity: 0-2-3 */
div li:nth-child(2) a:hover {
  border: 10px dashed black;
}

/* 7. specificity: 0-3-3 */
div div .nav:nth-child(2) a:hover {
  border: 10px double black;
}

a {
  display: inline-block;
  line-height: 40px;
  font-size: 20px;
  text-decoration: none;
  text-align: center;
  width: 200px;
  margin-bottom: 10px;
}

ul {
  padding: 0;
}

li {
  list-style-type: none;
}

这里发生了什么?首先,我们只关心这个例子的前七条规则,正如你所注意到的,我们在每条规则前都用注释标出了它们的优先级值。

  • 前两个选择器在争夺链接的 background-color 样式。第二个获胜,使背景色变为蓝色,因为它在选择器链中多了一个 ID 选择器:其优先级为 2-0-1,而另一个为 1-0-1。
  • 第 3 和第 4 个选择器在争夺链接文本的 color 样式。第二个获胜,使文本变为白色,因为虽然它少了一个元素选择器,但缺少的选择器被一个类选择器替换了,而类选择器的权重比元素选择器高。获胜的优先级是 1-1-3,而另一个是 1-0-4。
  • 第 5-7 个选择器在争夺链接悬停时的 border 样式。选择器 6 明显输给了选择器 5,优先级为 0-2-3 对 0-2-4;它在选择器链中少了一个元素选择器。然而,选择器 7 战胜了选择器 5 和 6,因为它与选择器 5 在选择器链中的子选择器数量相同,但一个元素被换成了一个类选择器。所以获胜的优先级是 0-3-3,而另外两个是 0-2-3 和 0-2-4。

备注: 每种选择器类型都有其自身的优先级级别,不能被优先级较低的选择器覆盖。例如,一百万个选择器组合在一起也无法覆盖一个 id 选择器的优先级。

评估优先级的最佳方法是从最高级别开始逐个比较优先级级别,必要时再比较下一个级别。只有当一个优先级列中的选择器分数打平时,才需要评估下一个更低的列;否则,你可以忽略优先级较低的选择器,因为它们永远无法覆盖优先级较高的选择器。

ID 与类

ID 选择器具有很高的优先级。这意味着基于匹配 ID 选择器应用的样式将覆盖基于其他选择器(包括类选择器和类型选择器)应用的样式。因为一个 ID 在一个页面上只能出现一次,并且 ID 选择器的优先级很高,所以最好给元素添加一个类而不是 ID。

如果使用 ID 是定位该元素的唯一方法——也许是因为你无法访问和编辑标记——可以考虑在属性选择器中使用 ID,例如 p[id="header"]

内联样式

内联样式,即 style 属性内的样式声明,其优先级高于所有普通样式,无论优先级如何。这类声明没有选择器,但它们的优先级可以被视为 1-0-0-0;总是高于任何其他优先级权重,无论选择器中有多少个 ID。

!important

有一段特殊的 CSS 可以用来覆盖以上所有的计算,甚至是内联样式——那就是 !important 标志。但是,在使用它时应该非常小心。这个标志用于使单个属性和值对成为最优先的规则,从而覆盖层叠的正常规则,包括普通的内联样式。

备注: 了解 !important 标志的存在是很有用的,这样当你在别人的代码中遇到它时能知道它是什么。然而,我们强烈建议你永远不要使用它,除非你绝对必须这样做。 !important 标志改变了层叠的正常工作方式,所以它会使调试 CSS 问题变得非常困难,尤其是在一个大型样式表中。

看下面这个例子,我们有两个段落,其中一个有 ID。

html
<p class="better">This is a paragraph.</p>
<p class="better" id="winning">One selector to rule them all!</p>
css
#winning {
  background-color: red;
  border: 1px solid black;
}

.better {
  background-color: gray;
  border: none !important;
}

p {
  background-color: blue;
  color: white;
  padding: 5px;
}

让我们逐步分析一下发生了什么——如果你觉得难以理解,可以试着移除一些属性看看会发生什么。

  1. 你会看到第三条规则的 colorpadding 值被应用了,但 background-color 没有。为什么?按理说,所有这三条都应该被应用,因为源顺序中靠后的规则通常会覆盖靠前的规则。
  2. 然而,在它之上的规则获胜了,因为类选择器的优先级高于元素选择器。
  3. 两个元素都有一个 betterclass,但第二个还有一个 winningid。由于 ID 的优先级比类更高,所以红色的 background-color1px blackborder 都应该被应用到第二个元素上,而第一个元素则会得到类所指定的灰色背景色,并且没有边框。
  4. 第二个元素确实得到了红色的 background-color,但没有 border。为什么?因为第二条规则中的 !important 标志。在 border: none 后面添加 !important 标志意味着这个声明将胜过前一条规则中的 border 值,即使 ID 选择器具有更高的优先级。

备注: 覆盖一个重要声明的唯一方法是在源顺序的后面包含另一个具有相同优先级的重要声明,或者一个具有更高优先级的重要声明。

有一种情况你可能不得不使用 !important 标志,那就是当你在一个 CMS 上工作,无法编辑核心 CSS 模块,而你又确实想要覆盖一个无法用其他方式覆盖的内联样式或重要声明时。但说真的,如果能避免就不要用它。

CSS 位置的影响

最后,需要注意的是,CSS 声明的优先级取决于它在哪个样式表中指定。

用户可以设置自定义样式表来覆盖开发者的样式。例如,一个视力受损的用户可能希望将他们访问的所有网页的字体大小设置为正常大小的两倍,以便于阅读。

声明覆盖顺序

冲突的声明将按以下顺序应用,后面的会覆盖前面的

  1. 用户代理样式表中的声明(例如,浏览器的默认样式,在没有设置其他样式时使用)。
  2. 用户样式表中的普通声明(用户设置的自定义样式)。
  3. 作者样式表中的普通声明(这些是我们,即 Web 开发者设置的样式)。
  4. 作者样式表中的重要声明。
  5. 用户样式表中的重要声明。
  6. 用户代理样式表中的重要声明。

备注: 对于标记了 !important 的样式,优先顺序是反过来的。Web 开发者的样式表覆盖用户样式表是合理的,这样可以保持设计的初衷;然而,有时用户有充分的理由来覆盖 Web 开发者的样式,如上所述,这可以通过在他们的规则中使用 !important 来实现。

总结

如果你理解了本文的大部分内容,那么做得很好——你已经开始熟悉 CSS 的基本机制了。

如果你没有完全理解层叠、优先级和继承,也不用担心!这绝对是我们在课程中迄今为止讲过的最复杂的内容,即使是专业的 Web 开发者有时也会觉得棘手。我们建议你在继续学习课程的过程中多回顾这篇文章,并不断思考它。

如果你开始遇到样式未按预期应用的奇怪问题,请回头参考这里。这可能是一个优先级问题。接下来,我们会提供一些测试,你可以用它们来检查你对我们提供的关于层叠的信息的理解和记忆程度。