特异性

Specificity 是浏览器用来确定 CSS 声明 对元素最相关的算法,它决定了应用于元素的属性值。Specificity 算法计算 CSS 选择器 的权重,以确定来自竞争 CSS 声明的哪个规则将应用于元素。

注意: 浏览器在确定 级联来源和重要性 之后才会考虑 specificity。换句话说,对于竞争的属性声明,specificity 仅在来自对属性具有优先级的 级联来源和层 的选择器之间相关且进行比较。当级联层中具有优先级的竞争声明的选择器 specificity 相等时,作用域邻近 和出现顺序将变得相关。

如何计算特异性?

Specificity 是一种算法,用于计算应用于给定 CSS 声明的权重。权重由匹配元素(或伪元素)的选择器中 每个权重类别中的选择器数量 决定。如果有两个或多个声明为同一个元素提供不同的属性值,则具有匹配选择器的样式块中的声明值将具有最大的算法权重并被应用。

Specificity 算法基本上是一个三列值,包含三个类别或权重 - ID、CLASS 和 TYPE,对应于三种类型的选择器。该值表示每个权重类别中匹配元素的选择器组件的数量,并写为 ID - CLASS - TYPE。这三列是通过计算与元素匹配的选择器中每个选择器权重类别中的选择器组件数量来创建的。

选择器权重类别

选择器权重类别按 specificity 递减的顺序列出

ID 列

仅包含 ID 选择器,例如 #example。对于匹配选择器中的每个 ID,将 1-0-0 添加到权重值。

CLASS 列

包括 类选择器,例如 .myClass,属性选择器,例如 [type="radio"][lang|="fr"],以及伪类,例如 :hover:nth-of-type(3n):required。 对于匹配选择器中的每个类、属性选择器或伪类,将 0-1-0 添加到权重值。

TYPE 列

包括 类型选择器,例如 ph1td,以及伪元素,例如 ::before::placeholder,以及所有其他带有双冒号表示法的选择器。 对于匹配选择器中的每个类型或伪元素,将 0-0-1 添加到权重值。

无值

通用选择器 (*) 以及伪类 :where() 及其参数在计算权重时不计算,因此其值为 0-0-0,但它们确实匹配元素。 这些选择器不会影响特异性权重值。

组合器,例如 +>~" "||,可能会使选择器在选择方面更具体,但它们不会向特异性权重添加任何值。

& 嵌套组合器不会增加特异性权重,但嵌套规则会。 在特异性和功能方面,嵌套与 :is() 伪类非常相似。

与嵌套类似,:is():has() 和否定 (:not()) 伪类本身不会添加任何权重。 然而,这些选择器中的参数确实会。 每个的权重来自选择器参数列表中特异性最高的那个选择器。 同样,对于嵌套选择器,嵌套选择器组件添加的特异性权重是逗号分隔的嵌套选择器列表中特异性最高的选择器。

:not():is():has() 和 CSS 嵌套例外情况 在下面讨论。

匹配选择器

特异性权重来自匹配选择器。 以这个具有三个逗号分隔选择器的 CSS 选择器为例

css
[type="password"],
input:focus,
:root #myApp input:required {
  color: blue;
}

上面的选择器列表中的 [type="password"] 选择器,特异性权重为 0-1-0,将 color: blue 声明应用于所有密码输入类型。

所有输入,无论类型如何,在获得焦点时,都会匹配列表中的第二个选择器 input:focus,其特异性权重为 0-1-1; 这个权重是由 :focus 伪类 (0-1-0) 和 input 类型 (0-0-1) 组成。 如果密码输入获得了焦点,它将匹配 input:focus,并且 color: blue 样式声明的特异性权重将为 0-1-1。 当该密码没有焦点时,特异性权重保持为 0-1-0

嵌套在具有 id="myApp" 属性的元素中的必需输入的特异性为 1-2-1,基于一个 ID,两个伪类和一个元素类型。

如果具有 required 的密码输入类型嵌套在具有 id="myApp" 设置的元素中,特异性权重将为 1-2-1,基于一个 ID,两个伪类和一个元素类型,无论它是否有焦点。 为什么在这种情况下特异性权重是 1-2-1 而不是 0-1-10-1-0? 因为特异性权重来自特异性权重最大的匹配选择器。 权重是通过从左到右比较三列中的值来确定的。

css
[type="password"]             /* 0-1-0 */
input:focus                   /* 0-1-1 */
:root #myApp input:required   /* 1-2-1 */

三列比较

确定相关选择器的特异性值后,从左到右比较每列中选择器组件的数量。

css
#myElement {
  color: green; /* 1-0-0  - WINS!! */
}
.bodyClass .sectionClass .parentClass [id="myElement"] {
  color: yellow; /* 0-4-0 */
}

第一列是ID 组件的值,它是每个选择器中 ID 的数量。 比较竞争选择器中ID 列中的数字。 无论其他列中的值如何,ID 列中值更大的选择器获胜。 在上面的示例中,即使黄色选择器总共包含更多组件,但只有第一列的值才重要。

如果竞争选择器中ID 列中的数字相同,则比较下一列CLASS,如下所示。

css
#myElement {
  color: yellow; /* 1-0-0 */
}
#myApp [id="myElement"] {
  color: green; /* 1-1-0  - WINS!! */
}

CLASS 列是选择器中类名、属性选择器和伪类的计数。 当ID 列的值相同时,无论TYPE 列中的值如何,CLASS 列中值更大的选择器获胜。 这在下面的示例中显示。

css
:root input {
  color: green; /* 0-1-1 - WINS because CLASS column is greater */
}
html body main input {
  color: yellow; /* 0-0-4 */
}

如果竞争选择器中CLASSID 列中的数字相同,则TYPE 列变得相关。 TYPE 列是选择器中元素类型和伪元素的数量。 当前两列的值相同时,TYPE 列中数字更大的选择器获胜。

如果竞争选择器在所有三列中都具有相同的值,则邻近规则生效,其中最后声明的样式具有优先级。

css
input.myClass {
  color: yellow; /* 0-1-1 */
}
:root input {
  color: green; /* 0-1-1 WINS because it comes later */
}

:is():not():has() 和 CSS 嵌套例外情况

匹配任何伪类 :is(),关系伪类 :has() 和否定伪类 :not() 在特异性权重计算中被视为伪类。 它们本身不会向特异性方程式添加任何权重。 但是,传递到伪类括号中的选择器参数是特异性算法的一部分; 特异性值计算中匹配任何和否定伪类的权重是参数的 权重

css
p {
  /* 0-0-1 */
}
:is(p) {
  /* 0-0-1 */
}

h2:nth-last-of-type(n + 2) {
  /* 0-1-1 */
}
h2:has(~ h2) {
  /* 0-0-2 */
}

div.outer p {
  /* 0-1-2 */
}
div:not(.inner) p {
  /* 0-1-2 */
}

请注意,在上面的 CSS 配对中,由 :is():has():not() 伪类提供的特异性权重是选择器参数的值,而不是伪类的值。

所有这三个伪类都接受复杂选择器列表,一个逗号分隔的选择器列表,作为参数。 此功能可用于提高选择器的特异性

css
:is(p, #fakeId) {
  /* 1-0-0 */
}
h1:has(+ h2, > #fakeId) {
  /* 1-0-1 */
}
p:not(#fakeId) {
  /* 1-0-1 */
}
div:not(.inner, #fakeId) p {
  /* 1-0-2 */
}

在上面的 CSS 代码块中,我们在选择器中包含了 #fakeId。 此 #fakeId1-0-0 添加到每个段落的特异性权重。

当使用 CSS 嵌套 创建复杂选择器列表时,其行为与 :is() 伪类完全相同。

css
p,
#fakeId {
  span {
    /* 1-0-1 */
  }
}

在上面的代码块中,复杂选择器 p, #fakeId 的特异性来自 #fakeId 以及 span,因此这对 p span#fakeId span 创建了 1-0-1 的特异性。 这与 :is(p, #fakeId) span 选择器的特异性相同。

通常,您希望将特异性保持在最低限度,但如果您需要出于特定原因提高元素的特异性,这三个伪类可以提供帮助。

css
a:not(#fakeId#fakeId#fakeID) {
  color: blue; /* 3-0-1 */
}

在此示例中,所有链接都将为蓝色,除非被具有 3 个或更多 ID 的链接声明覆盖,匹配 a 的颜色值包含 !important 标志,或者如果链接具有 内联样式 颜色声明。 如果您使用此类技术,请添加注释以解释为什么需要此技巧。

内联样式

添加到元素的内联样式(例如,style="font-weight: bold;")始终会覆盖作者样式表中的任何普通样式,因此,可以认为它们具有最高的特异性。 将内联样式视为具有 1-0-0-0 的特异性权重。

覆盖内联样式的唯一方法是使用 !important

许多 JavaScript 框架和库添加了内联样式。 使用 !important 与非常有针对性的选择器(例如使用内联样式的属性选择器)是覆盖这些内联样式的一种方法。

html
<p style="color: purple"></p>
css
p[style*="purple"] {
  color: rebeccapurple !important;
}

确保在包含重要标志的每个位置都包含注释,以便代码维护人员了解为什么使用 CSS 反模式,并且知道不要覆盖它。

!important 例外

标记为重要的 CSS 声明会覆盖相同级联层和来源中的任何其他声明。 虽然在技术上,!important 与特异性无关,但它与特异性和级联直接交互。 它反转了样式表的 级联 顺序。

如果来自相同来源和级联层的声明发生冲突,并且一个属性值设置了 !important 标志,则无论特异性如何,都将应用重要声明。 当来自相同来源和级联层并且具有 !important 标志的冲突声明应用于同一元素时,将应用特异性更高的声明。

使用 !important 覆盖特异性被认为是不良做法,应避免为此目的使用它。 了解和有效地使用特异性和级联可以消除对 !important 标志的任何需求。

不要使用 !important 覆盖外部 CSS(来自外部库,如 Bootstrap 或 normalize.css),而是将第三方脚本直接导入 级联层。 如果你必须在你的 CSS 中使用 !important,请注释你的用法,以便未来的代码维护人员知道为什么声明被标记为重要,并且知道不要覆盖它。 但是,在编写其他开发人员需要合并而无法控制的插件或框架时,绝对不要使用 !important

:where() 例外

特异性调整伪类 :where() 的特异性始终被替换为零,0-0-0。 它能够在不增加特异性的情况下,使 CSS 选择器对目标元素非常具体。

在创建供没有访问权限编辑你的 CSS 的开发人员使用的第三方 CSS 时,创建特异性尽可能低的 CSS 被认为是一种良好的做法。 例如,如果你的主题包含以下 CSS

css
:where(#defaultTheme) a {
  /* 0-0-1 */
  color: red;
}

那么实现小部件的开发人员可以轻松地仅使用类型选择器覆盖链接颜色。

css
footer a {
  /* 0-0-2 */
  color: blue;
}

@scope 块如何影响特异性

将规则集包含在 @scope 块中不会影响其选择器的特异性,无论在范围根和限制内使用什么选择器。 例如

css
@scope (.article-body) {
  /* img has a specificity of 0-0-1, as expected */
  img { ... }
}

但是,如果您决定将 :scope 伪类显式地附加到您的范围选择器,则在计算其特异性时需要将其考虑在内。 :scope 与所有常规伪类一样,具有 0-1-0 的特异性。 例如

css
@scope (.article-body) {
  /* :scope img has a specificity of 0-1-0 + 0-0-1 = 0-1-1 */
  :scope img { ... }
}

@scope 块中使用 & 选择器时,& 代表范围根选择器; 它会在内部被重写为包装在 :is() 选择器中的该选择器。 因此,例如,在

css
@scope (figure, #primary) {
  & img { ... }
}

& img 等同于 :is(figure, #primary) img

由于 :is() 采用其最具体参数(在本例中为 #primary)的特异性,因此范围 & img 选择器的特异性因此为 1-0-0 + 0-0-1 = 1-0-1。

处理特异性问题的技巧

除了使用 !important 之外,还可以考虑使用级联层并在整个 CSS 中使用低权重特异性,以便使用稍微更具体规则可以轻松地覆盖样式。 使用语义 HTML 有助于提供应用样式的锚点。

在添加和不添加特异性的情况下使选择器更具体

通过在选择元素之前指明正在设置样式的文档部分,规则变得更具体。 根据你的添加方式,你可以添加一些、很多或不添加特异性,如下所示

html
<main id="myContent">
  <h1>Text</h1>
</main>
css
#myContent h1 {
  color: green; /* 1-0-1 */
}
[id="myContent"] h1 {
  color: yellow; /* 0-1-1 */
}
:where(#myContent) h1 {
  color: blue; /* 0-0-1 */
}

无论顺序如何,标题都将为绿色,因为该规则是最具体的。

降低 ID 特定性

特定性基于选择器的形式。将元素的id作为属性选择器而不是 ID 选择器包含在内,是使元素更特定而不添加过多特定性的好方法。在前面的示例中,选择器[id="myContent"]被视为属性选择器,用于确定选择器的特定性,即使它选择了 ID。

如果您需要使选择器更特定,但又不想添加任何特定性,也可以将id或选择器的任何部分作为参数包含在:where()特定性调整伪类中。

通过复制选择器来增加特定性

作为增加特定性的一个特例,您可以复制CLASSID列中的权重。在复合选择器中重复 id、类、伪类或属性选择器将增加特定性,以便在覆盖您无法控制的非常特定的选择器时。

css
#myId#myId#myId span {
  /* 3-0-1 */
}
.myClass.myClass.myClass span {
  /* 0-3-1 */
}

谨慎使用,如果有的话。如果使用选择器重复,请始终对您的 CSS 进行注释。

通过使用:is():not()(以及:has()),即使您无法向父元素添加id,也可以提高特定性。

css
:not(#fakeID#fakeId#fakeID) span {
  /* 3-0-1 */
}
:is(#fakeID#fakeId#fakeID, span) {
  /* 3-0-0 */
}

优先于第三方 CSS

利用级联层是使一组样式优先于另一组样式的标准方法;级联层使这成为可能,而无需使用特定性!导入到级联层中的普通(非重要)作者样式比非分层作者样式的优先级低。

如果样式来自您无法编辑或不理解的样式表,并且您需要覆盖样式,则可以使用一种策略将您无法控制的样式导入到级联层中。后续声明的层中的样式具有优先级,非分层样式比来自相同来源的所有分层样式具有优先级。

当来自不同层的两个选择器匹配同一个元素时,来源和重要性优先;失败的样式表的特定性与选择器的特定性无关。

html
<style>
  @import TW.css layer();
  p,
  p * {
    font-size: 1rem;
  }
</style>

在上面的示例中,所有段落文本,包括嵌套内容,都将是1rem,无论段落有多少类名称与 TW 样式表匹配。

避免和覆盖!important

最好的方法是不使用!important。以上关于特定性的解释应该有助于避免使用该标志,并在遇到时完全将其删除。

要消除对!important的感知需求,您可以执行以下操作之一

  • 提高以前!important声明的选择器的特定性,使其大于其他声明。
  • 给予它相同的特定性,并将其放在它要覆盖的声明之后。
  • 降低要覆盖的选择器的特定性。

所有这些方法都在前面的部分中介绍。

如果您无法从作者样式表中删除!important标志,则覆盖重要样式的唯一解决方案是使用!important。创建重要的声明覆盖的级联层是一个很好的解决方案。有两种方法可以做到这一点,包括

方法 1

  1. 创建一个单独的简短样式表,其中只包含重要的声明,专门覆盖您无法删除的任何重要声明。
  2. 使用layer()将此样式表作为第一个导入导入到您的 CSS 中,包括@import语句,然后再链接到其他样式表。这是为了确保重要的覆盖作为第一层导入。
html
<style>
  @import importantOverrides.css layer();
</style>

方法 2

  1. 在您的样式表声明的开头,创建一个命名的级联层,如下所示
    css
    @layer importantOverrides;
    
  2. 每次您需要覆盖重要的声明时,都在命名的层中声明它。只在层中声明重要的规则。
    css
    [id="myElement"] p {
      /* normal styles here */
    }
    @layer importantOverrides {
      [id="myElement"] p {
        /* important style here */
      }
    }
    

层中重要样式的选择器的特定性可以很低,只要它与您要覆盖的元素匹配即可。普通层应该在层外声明,因为分层样式比非分层样式的优先级低。

树邻近性忽略

元素与给定选择器中引用的其他元素的邻近性对特定性没有影响。

css
body h1 {
  color: green;
}

html h1 {
  color: purple;
}

<h1>元素将是紫色的,因为当声明具有相同的特定性时,最后声明的选择器具有优先级。

直接目标元素与继承样式

直接目标元素的样式始终优先于继承的样式,无论继承规则的特定性如何。给定以下 CSS 和 HTML

css
#parent {
  color: green;
}

h1 {
  color: purple;
}
html
<html lang="en">
  <body id="parent">
    <h1>Here is a title!</h1>
  </body>
</html>

h1将是紫色的,因为h1选择器专门针对元素,而绿色是从#parent声明中继承的。

示例

在以下 CSS 中,我们有三个选择器针对<input>元素来设置颜色。对于给定的输入,具有优先级的颜色声明的特定性权重是具有最大权重的匹配选择器。

css
#myElement input.myClass {
  color: red;
} /* 1-1-1 */
input[type="password"]:required {
  color: blue;
} /* 0-2-1 */
html body main input {
  color: green;
} /* 0-0-4 */

如果以上选择器都针对同一个输入,则输入将是红色的,因为第一个声明在ID列中具有最高值。

最后一个选择器有四个TYPE组件。虽然它具有最高的整数价值,但无论包含多少个元素和伪元素,即使有 150 个,TYPE组件永远不会优先于CLASS组件。当列值相等时,从左到右开始比较列值。

如果我们将上面的示例代码中的 id 选择器转换为属性选择器,则前两个选择器将具有相同的特定性,如下所示

css
[id="myElement"] input.myClass {
  color: red;
} /* 0-2-1 */
input[type="password"]:required {
  color: blue;
} /* 0-2-1 */

当多个声明具有相同的特定性时,CSS 中找到的最后一个声明将应用于元素。如果两个选择器都匹配同一个<input>,则颜色将是蓝色的。

附加说明

关于特定性的一些注意事项

  1. 特定性仅在同一级联层或来源中的多个声明针对同一个元素时才适用。特定性仅适用于相同重要性和相同来源和级联层的声明。如果匹配的选择器位于不同的来源,则级联决定哪个声明具有优先级。
  2. 当同一级联层和来源中的两个选择器具有相同的特定性时,会计算作用域邻近性;具有最低作用域邻近性的规则集获胜。有关更多详细信息和示例,请参阅如何解决@scope冲突
  3. 如果作用域邻近性对于两个选择器也相同,则源顺序就会起作用。当所有其他条件都相等时,最后一个选择器获胜。
  4. 根据 CSS 规则,直接目标元素始终优先于元素从其祖先继承的规则。
  5. 文档树中元素的邻近性对特定性没有影响。

规范

规范
选择器级别 4
# specificity-rules

另请参阅