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,以及它们的来源。暂时忽略层,层叠顺序如下
    顺序(从低到高) 来源 重要性
    1 用户代理(浏览器) 普通
    2 用户 普通
    3 作者(开发者) 普通
    4 CSS @keyframe 动画
    5 作者(开发者) !important
    6 用户 !important
    7 用户代理(浏览器) !important
    8 CSS 过渡
  3. 特异性:如果来源相同,则会考虑规则的特异性来选择一个值或另一个值。比较选择器的特异性,特异性最高的声明获胜。
  4. 作用域临近性:当优先级相同的来源层中的两个选择器具有相同特异性时,作用域规则中到作用域根的DOM层次结构向上跳跃次数最少的属性值获胜。有关更多详细信息和示例,请参阅如何解决@scope冲突
  5. 出现顺序:在优先级相同的来源中,如果某个属性存在竞争值,这些值位于匹配特异性和作用域临近性相同的选择器的样式块中,则应用样式顺序中的最后一个声明。

层叠顺序是升序,这意味着动画优先于普通值,无论这些值是在用户、作者还是用户代理样式中声明的,重要值优先于动画,过渡优先于重要值。

注意: 过渡和动画

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

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

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

基本示例

在更深入地了解层叠层如何影响层叠之前,让我们来看一个涉及跨各种来源的多个 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将被移除考虑。

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

作者样式表中有三个声明

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 - 第一个层 普通
2 B - 第二个层 普通
3 C - 最后一个层 普通
4 所有未分层样式 普通
5 内联style 普通
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的样式,层级优先级顺序相反。在层中声明的重要样式优先于在层之外声明的重要样式。在早期层中出现的重要值优先于在后续层叠层中声明的重要样式。

内联样式

仅与作者样式相关的是内联样式,使用style属性声明。普通内联样式优先于任何其他普通作者样式,无论选择器的特异性如何。如果在五个导入的样式表中的任何一个的:root body p选择器块中声明了line-height: 2;,则行高仍将为1.6

普通内联样式优先于任何其他普通作者样式,除非属性正在被 CSS 动画更改。

所有重要的内联样式优先于所有作者样式,无论是重要的还是不重要的,内联的还是不内联的,分层的还是不分层的。重要样式也优先于动画属性,但不优先于过渡属性。有三件事可以覆盖重要的内联样式:1) 重要的用户样式,2) 重要的用户代理样式,或 3) 正在过渡的属性值。

重要性和层

对于重要样式,来源类型优先级顺序相反。在任何层叠层之外声明的重要样式优先级低于那些作为层的一部分声明的样式。出现在早期层中的重要值的优先级高于在后续层叠层中声明的重要样式。

例如以下 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 用户代理 - 首次声明的层 普通
用户代理 - 最后声明的层
用户代理 - 未分层的样式
2 用户 - 首次声明的层 普通
用户 - 最后声明的层
用户 - 未分层的样式
3 作者 - 首次声明的层 普通
作者 - 最后声明的层
作者 - 未分层的样式
内联style
4 动画
5 作者 - 未分层的样式 !important
作者 - 最后声明的层
作者 - 首次声明的层
内联style
6 用户 - 未分层的样式 !important
用户 - 最后声明的层
用户 - 首次声明的层
7 用户代理 - 未分层的样式 !important
用户代理 - 最后声明的层
用户代理 - 首次声明的层
8 过渡

哪些 CSS 实体参与层叠

只有 CSS 属性/值对声明参与级联。CSS @规则描述符不参与级联,HTML 表现属性也不属于级联的一部分。

At规则

包含声明之外的实体的 CSS @规则,例如包含描述符@font-face规则,不参与级联。

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

虽然大多数@规则(例如@media@document@supports中的@规则)中包含的声明参与级联,但@规则可能会使整个选择器变得不相关,就像我们在基本示例中看到的打印样式一样。

@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@规则定义状态之间的动画。关键帧不级联,这意味着在任何给定时间,CSS 仅从一个@keyframes中获取值,并且永远不会混合多个关键帧。

如果使用相同的动画名称定义了多个关键帧动画,则具有最高优先级的来源和层中的最后一个定义的@keyframes将被使用。即使@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 级联和继承级别 4

另请参阅