CSS层叠简介

层叠是一种算法,用于定义用户代理如何组合来自不同源的属性值。当来自多个层叠层@scope块中的声明为元素设置属性值时,层叠定义了哪个源和层具有优先权。

层叠是CSS的核心,正如其名称所强调的:层叠样式表。当选择器匹配一个元素时,即使来自较低优先级源或层的选择器具有更高的特异度,具有最高优先级源的属性值也会被应用。

本文解释了层叠是什么以及CSS声明的层叠顺序,涵盖了层叠层和源类型。理解源优先级是理解层叠的关键。

源类型

CSS层叠算法的任务是选择CSS声明,以确定CSS属性的正确值。CSS声明来自不同的源类型:用户代理样式表作者样式表用户样式表

尽管样式表来自这些不同的源,并且可以位于每个源中的不同中,但它们在默认作用域方面存在重叠;为了使之工作,层叠算法定义了它们如何交互。在讨论交互之前,我们将在接下来的几节中定义一些关键术语。

用户代理样式表

用户代理或浏览器具有基本的样式表,为任何文档提供默认样式。这些样式表被称为用户代理样式表。大多数浏览器为此目的使用实际的样式表,而其他浏览器则在代码中模拟它们。最终结果是相同的。

有些浏览器允许用户修改用户代理样式表,但这很少见,也无法控制。

尽管HTML规范对用户代理样式表设置了一些限制,但浏览器有很大的自由度:这意味着浏览器之间存在一些差异。为了简化开发过程,Web开发人员可能会使用CSS重置样式表,例如normalize.css,它在开始根据特定需求进行修改之前,将所有浏览器的常见属性值设置为已知状态。

除非用户代理样式表在属性旁边包含!important,使其成为“重要的”,否则作者样式(包括重置样式表)声明的样式优先于用户代理样式,无论相关选择器的特异度如何。

作者样式表

作者样式表是最常见的样式表类型;这些是Web开发人员编写的样式。如上所述,这些样式可以重置用户代理样式,并定义给定网页或应用程序设计的样式。作者(或Web开发人员)使用一个或多个链接或导入的样式表、<style>块以及使用style属性定义的内联样式来定义文档的样式。这些作者样式定义了网站的外观和感觉——它的主题。

用户样式表

在大多数浏览器中,网站的用户(或读者)可以选择使用自定义的用户样式表来覆盖样式,以根据用户的意愿定制体验。根据用户代理,用户样式可以直接配置或通过浏览器扩展添加。

层叠层

层叠顺序基于源类型。每个源类型内的层叠基于该类型内层叠层的声明顺序。对于所有源——用户代理、作者或用户——样式可以在命名层或匿名层内部或外部声明。当使用layerlayer()@layer声明时,样式被放置到指定的命名层中,如果未提供名称,则放置到匿名层中。在层外声明的样式被视为匿名最后声明层的一部分。

让我们先看看层叠源类型,然后再深入探讨每个源类型中的层叠层。

层叠顺序

层叠算法确定如何为每个文档元素的每个属性找到要应用的值。以下步骤适用于层叠算法

  1. 相关性:它首先过滤来自不同源的所有规则,只保留适用于给定元素的规则。这意味着选择器匹配给定元素且是适当media at-rule一部分的规则。

  2. 源和重要性:然后它根据其重要性(即是否带有!important)和其源对这些规则进行排序。暂时忽略层,层叠顺序如下

    优先级顺序(从低到高) Origin 重要性
    1 用户代理(浏览器) normal
    2 用户 normal
    3 作者(开发者) normal
    4 CSS关键帧动画
    5 作者(开发者) !important
    6 用户 !important
    7 用户代理(浏览器) !important
    8 CSS 过渡
  3. 特异度:在源相同的情况下,规则的特异度被考虑用于选择一个值或另一个值。比较选择器的特异度,特异度最高的声明获胜。

  4. 作用域邻近度:当具有优先级的源层中的两个选择器具有相同的特异度时,作用域规则中到作用域根的DOM层次结构跳数最少的属性值获胜。有关详细信息和示例,请参阅@scope冲突如何解决

  5. 出现顺序:在具有优先级的源中,如果属性存在竞争值,并且这些值位于样式块中,匹配具有相同特异度和作用域邻近度的选择器,则样式顺序中最后的声明将被应用。

层叠是升序的,这意味着

  • 动画优先于正常值,无论是在用户、作者还是用户代理样式中声明。
  • 重要值优先于动画,无论是在用户、作者还是用户代理样式中声明。
  • 过渡优先于重要值。

注意: 过渡和动画

由动画@keyframes设置的属性值比所有正常样式(未设置!important的样式)更重要。

transition中设置的属性值优先于所有其他设置值,即使是那些标记为!important的值。

层叠算法在特异度算法之前应用,这意味着如果用户样式表(第2行)中声明了:root p { color: red;},而作者样式表(第3行)中声明了特异度较低的p {color: blue;},则段落将显示为蓝色。

基本示例

在深入探讨层叠层如何影响层叠之前,让我们看一个涉及不同源的多个CSS源的示例,并逐步完成层叠算法的步骤

这里我们有一个用户代理样式表,两个作者样式表和一个用户样式表,HTML中没有内联样式

用户代理 CSS

css
li {
  margin-left: 10px;
}

作者 CSS 1

css
li {
  margin-left: 0;
} /* This is a reset */

作者 CSS 2

css
@media screen {
  li {
    margin-left: 3px;
  }
}

@media print {
  li {
    margin-left: 1px;
  }
}

@layer namedLayer {
  li {
    margin-left: 5px;
  }
}

用户 CSS

css
.specific {
  margin-left: 1em;
}

HTML

html
<ul>
  <li class="specific">1<sup>st</sup></li>
  <li>2<sup>nd</sup></li>
</ul>

在这种情况下,li.specific规则内的声明应该适用。

层叠算法再次有五个步骤,按顺序排列

  1. 相关性
  2. 源和重要性
  3. 优先级
  4. 作用域邻近度
  5. 出现顺序

1px用于打印媒体。由于其媒体类型缺乏相关性,它被排除在考虑之外。

没有声明标记为!important,因此优先级顺序是作者样式表高于用户样式表高于用户代理样式表。根据源和重要性,用户样式表中的1em和用户代理样式表中的10px被排除在考虑之外。

请注意,即使用户样式中.specific上的1em具有更高的特异度,它也是用户样式表中的一个普通声明。因此,它的优先级低于任何作者样式,并在算法的源和重要性步骤中被移除,甚至在特异度发挥作用之前。

作者样式表中有三个声明

css
li {
  margin-left: 0;
} /* from author css 1 */
css
@media screen {
  li {
    margin-left: 3px;
  }
}
css
@layer namedLayer {
  li {
    margin-left: 5px;
  }
}

最后一个,5px是层叠层的一部分。层中的正常声明的优先级低于相同源类型中不在层中的正常样式。这也通过算法的第2步,源和重要性,被移除。

这留下了03px,它们都具有相同的选择器,因此具有相同的特异度。它们都不在@scope块中,因此在此示例中作用域邻近度也不起作用。

然后我们查看出现顺序。第二个,即两个未分层作者样式中最后一个,获胜。

css
margin-left: 3px;

注意: 用户CSS中定义的声明,即使它可能具有更高的特异度,也不会被选择,因为层叠算法的源和重要性特异度算法之前应用。在层叠层中定义的声明,即使它可能在代码中出现较晚,也不会优先,因为层叠层中的正常样式优先级低于正常的未分层样式。出现顺序仅在源、重要性和特异度都相等时才重要。

作者样式:内联样式、层和优先级

层叠顺序表提供了优先级顺序概述。该表将用户代理、用户和作者源类型样式总结为每行两行,分别是“源类型 - 正常”和“源类型 - !important”。每个源类型内的优先级更为细微。样式可以包含在其源类型内的层中,对于作者样式,还存在内联样式在层叠顺序中的位置问题。

声明层的顺序在确定优先级方面很重要。层中的正常样式优先于在先前层中声明的样式;而未在任何层中声明的正常样式优先于正常分层样式,无论特异度如何。

在此示例中,作者使用CSS的@import规则在<style>信息元素中导入了五个外部样式表。

html
<style>
  @import "unlayeredStyles.css";
  @import "AStyles.css" layer(A);
  @import "moreUnlayeredStyles.css";
  @import "BStyles.css" layer(B);
  @import "CStyles.css" layer(C);
  p {
    color: red;
    padding: 1em !important;
  }
</style>

然后在文档正文中我们有内联样式

html
<p style="line-height: 1.6em; text-decoration: overline !important;">Hello</p>

在上面的CSS代码块中,按顺序创建了名为“A”、“B”和“C”的三个层叠层。三个样式表被直接导入到层中,两个在没有创建或分配到层的情况下导入。下面列表中的“所有未分层样式”(正常作者样式优先级 - 顺序4)包括来自这两个样式表和额外的未分层CSS样式块的样式。此外,还有两个内联样式,一个正常的line-height声明和一个重要的text-decoration声明

优先级顺序(从低到高) 作者样式 重要性
1 A - 第一层 normal
2 B - 第二层 normal
3 C - 最后一层 normal
4 所有未分层样式 normal
5 内联style normal
6 动画
7 所有未分层样式 !important
8 C - 最后一层 !important
9 B - 第二层 !important
10 A - 第一层 !important
11 内联style !important
12 过渡

在所有源类型中,层中包含的正常样式优先级最低。在我们的示例中,与第一个声明层(A)相关的正常样式优先级低于第二个声明层(B)中的正常样式,后者又低于第三个声明层(C)中的正常样式。这些分层样式优先级低于所有正常未分层样式,其中包括来自unlayeredStyles.cssmoreUnlayeredStyles.css<style>本身中pcolor的正常样式。

如果A、B或C中的任何分层样式具有与元素匹配的更高特异度的选择器,类似于:root body p { color: black; },则无关紧要。这些声明因而被排除在考虑之外;正常的堆叠样式优先级低于正常的未堆叠样式。但是,如果在unlayeredStyles.css中找到了更具体的选择器:root body p { color: black; },因为源和重要性具有相同的优先级,特异度将意味着更具体的黑色声明将获胜。

对于声明为!important的样式,层的优先级顺序是颠倒的。在层中找到的重要声明优先于在层外找到的重要声明。在第一层(A)中找到的重要声明优先于在B层中找到的重要声明,后者又优先于在C层中找到的重要声明,而C层又优先于在层外找到的重要声明。

内联样式

仅与作者样式相关的内联样式,使用style属性声明。正常内联样式优先于任何其他正常作者样式,无论选择器的特异度如何。如果line-height: 2;在任何五个导入的样式表中的:root body p选择器块中声明,行高仍然是1.6。正常内联样式不优先于动画或过渡属性。

重要的内联样式优先于所有其他作者样式,无论它们是重要的、内联的还是分层的。重要的内联样式也优先于动画属性,但不优先于过渡属性。有三件事可以覆盖重要的内联样式

  • 重要的用户样式。
  • 重要的用户代理样式。
  • 过渡属性。

重要性和层

对于重要样式,源类型优先级顺序是颠倒的。在任何层叠层之外声明的重要样式优先级低于作为层的一部分声明的样式。早期层中出现的重要样式优先于后续层叠层中声明的重要样式。

例如以下CSS

css
p {
  color: red;
}

@layer B {
  :root p {
    color: blue;
  }
}

尽管红色是首先声明的,并且选择器特异度较低,但由于未分层CSS优先于分层CSS,段落将是红色。如果我们在段落上包含一个内联样式将其设置为不同的颜色,例如<p style="color: black">,则段落将是黑色。

当我们向这段CSS添加!important时,优先级顺序与样式表相反

css
p {
  color: red !important;
}

@layer B {
  :root p {
    color: blue !important;
  }
}

现在段落将是蓝色。最早声明的层中的!important优先于后续层和未分层的重要声明。如果内联样式包含!important,例如<p style="color: black !important">,则段落将再次是黑色。内联重要性确实优先于所有其他作者声明的!important声明,无论特异度如何。

注意: !important标志会颠倒层叠层的优先级。因此,尽量不要使用!important来覆盖外部样式。相反,请使用@import以及layer关键字或layer()函数将外部样式表(来自框架、小部件样式表、库等)导入到层中。将样式表作为CSS中的第一个声明导入到层中会降低它们的优先级,而稍后在CSS中定义的作者定义的层将具有更高的优先级。!important标志应该很少使用,如果使用的话,也只是在第一个声明层中,用于保护所需的样式不被后来的覆盖。

正在过渡的样式优先于所有重要样式,无论它们是如何声明的。

完整的层叠顺序

现在我们对源类型和层叠层优先级有了更好的理解,我们意识到层叠顺序中的表格可以更准确地表示为以下表格

优先级顺序
(从低到高)
样式源重要性
1用户代理 - 首先声明的层normal
用户代理 - 最后声明的层
用户代理 - 未分层样式
2用户 - 首先声明的层normal
用户 - 最后声明的层
用户 - 未分层样式
3作者 - 首先声明的层normal
作者 - 最后声明的层
作者 - 未分层样式
内联style
4动画
5作者 - 未分层样式!important
作者 - 最后声明的层
作者 - 首先声明的层
内联style
6用户 - 未分层样式!important
用户 - 最后声明的层
用户 - 首先声明的层
7用户代理 - 未分层样式!important
用户代理 - 最后声明的层
用户代理 - 首先声明的层
8过渡

哪些CSS实体参与层叠

只有CSS属性/值对声明参与层叠。CSS at-rule描述符不参与层叠,HTML表示性属性不是层叠的一部分。

@ 规则

包含除声明之外的实体的CSS at-rules,例如包含描述符@font-face规则,不参与层叠。

在大多数情况下,at-rule中定义的属性和描述符不参与层叠。只有at-rule作为一个整体参与层叠。例如,在@font-face规则中,字体名称由font-family描述符标识。如果定义了几个具有相同描述符的@font-face规则,则只考虑最合适的@font-face作为一个整体。如果多个完全合适,则使用算法的步骤1、2和4比较整个@font-face声明(at-rule没有特异度)。

虽然大多数at-rule中包含的声明——例如@media@document@supports中的声明——参与层叠,但at-rule可能会使整个选择器不相关,正如我们在基本示例中的打印样式所看到的那样。

@keyframes中的声明不参与层叠。与@font-face一样,只有@keyframes作为一个整体通过层叠算法进行选择。动画的优先级顺序在下面描述

当涉及@import时,@import本身不参与层叠,但所有导入的样式都参与。如果@import定义了命名层或匿名层,则导入的样式表的内容将放置到指定的层中。所有其他使用@import导入的CSS都被视为最后声明的层。这在上面已经讨论过。

最后,@charset遵循特定的算法,不受层叠算法的影响。

表示性属性

表示性属性是源文档中可以影响样式的属性。例如,当包含时,已废弃的align属性设置了几个HTML元素的对齐方式,而fill属性定义了用于绘制SVG形状和文本的颜色,并定义了SVG动画的最终状态。虽然它们是作者样式,但表示性属性不参与层叠。

如果用户代理支持HTML表示性属性,则HTML和SVG中包含的有效表示性属性,例如alignfill属性,会被转换为相应的CSS规则(所有SVG表示性属性都支持作为CSS属性),并以特异度等于0的方式插入到作者样式表中的任何其他样式之前。

表示性属性不能声明为!important

CSS动画和层叠

CSS动画,使用@keyframes at-rules,定义了状态之间的动画。@keyframes不层叠,这意味着在任何给定时间,CSS只从一组@keyframes中获取值,并且从不混合多个。如果定义了多个具有相同动画名称的@keyframes集,则使用具有最高优先级的源和层中最后定义的一组。其他@keyframes被忽略,即使它们动画化不同的属性。

css
p {
  animation: infinite 5s alternate repeatedName;
}

@keyframes repeatedName {
  from {
    font-size: 1rem;
  }
  to {
    font-size: 3rem;
  }
}

@layer A {
  @keyframes repeatedName {
    from {
      background-color: yellow;
    }
    to {
      background-color: orange;
    }
  }
}

@layer B {
  @keyframes repeatedName {
    from {
      color: white;
    }
    to {
      color: black;
    }
  }
}

在此示例中,有三个名为repeatedName的独立动画声明。当animation: infinite 5s alternate repeatedName应用于段落时,只应用一个动画:基于源和层优先级顺序,未分层CSS中定义的关键帧动画优先于分层关键帧动画声明。在此示例中,只有元素的字体大小会被动画化。

注意: 没有重要的动画,因为@keyframes块中包含!important作为值一部分的属性声明会被忽略。

重置样式

在内容修改样式完成后,它可能会发现自己需要将它们恢复到已知状态。这可能发生在动画、主题更改等情况下。CSS属性all允许您快速将CSS中的(几乎)所有内容恢复到已知状态。

all允许您选择立即将所有属性恢复到其初始(默认)状态、从层叠的上一级继承的状态、特定源(用户代理样式表、作者样式表或用户样式表)的状态,甚至完全清除属性的值。

规范

规范
CSS 层叠与继承第四级

另见