CSS 特异性、继承和级联
名词:
- 特异性 =
specificity
- 继承 =
inheritance
- 级联 =
cascade
继承是将某些属性值从一个元素传递到其后代元素的机制。在确定哪些值应该应用于某个元素时,用户代理不仅要考虑继承,还要考虑声明的特异性以及声明本身的来源。这种"考虑"过程被称为级联.
1. 特异性
这里我们要讨论的问题是, 当有一个 UI 元素同时被多个 css 选择器选中, 它的显示效果是怎么样的?
比如:
/* case 1 */
h1 {color: red;}
body h1 {color: green;}
/* case 2 */
h2.grape {color: purple;}
h2 {color: silver;}
/* case 3 */
html > body table tr[id="totals"] td ul > li {color: maroon;}
li#answer {color: navy;}
答案就在每个选择器的 特异性 中.
对于每条规则 (rule),用户代理(即 Web 浏览器)都会计算该选择器的特异性,并将特异性附加到级联层中优先级最高的规则中的每个声明。当一个元素有两个或多个冲突的属性声明时,特异性最高的声明将胜出。
For every rule, the user agent (i.e., a web browser) evaluates the specificity of the selector and attaches the specificity to each declaration in the rule within the cascade layer that has precedence. When an element has two or more conflicting property declarations, the one with the highest specificity will win out.
这并不是冲突解决的全部内容,它实际上要复杂得多。目前,只需记住,选择器特异性仅与共享相同源和级联层的其他选择器进行比较。
选择器的特异性由其本身的组件决定。特殊性值可以用三部分表示,例如:0,0,0
。选择器的实际特殊性由以下公式确定:
- 对于选择器中给出的每个 ID 属性值,增加
1,0,0
。 - 对于选择器中给出的每个类属性值(class attribute)、属性选择或伪类,增加
0,1,0
。 - 对于选择器中给出的每个元素和伪元素,增加
0,0,1
。 - 组合器 (combinator) 不影响特异性值。
:where()
伪类和通用选择器中列出的任何内容都会增加0,0,0
。(虽然它们不会对特异性权重做出任何贡献,但它们确实会匹配元素,这一点与组合器不同。):is()
,:not()
或:has()
伪类的特异性等于其选择器列表参数中最具体的选择器的特异性。
例子:
h1 {color: red;} /* specificity = 0,0,1 */
p em {color: purple;} /* specificity = 0,0,2 */
.grape {color: purple;} /* specificity = 0,1,0 */
*.bright {color: yellow;} /* specificity = 0,1,0 */
p.bright em.dark {color: maroon;} /* specificity = 0,2,2 */
#id216 {color: blue;} /* specificity = 1,0,0 */
*:is(aside#warn, code) {color: red;} /* specificity = 1,0,1 */
div#sidebar *[href] {color: silver;} /* specificity = 1,1,1 */
如果一个 em
元素同时被第一条和第五条规则匹配上, 那么最终的显示的颜色会是 maroon
, 因为 0,2,2 > 0,0,1
.
注意倒数第二条规则 *:is(aside#warn, code)
, :is()
是一个伪类, 其特异性等于选择器列表中最具体的选择器。 这里, aside#warn
的特异性值是 1,0,1
, 而 code
选择器的特异性值是 0,0,1
, 因此整个规则的特异性之等于 aside#warn
的特异性值,等于 1,0,1
.
这里我们再回头来看看我们在文章开始时展示的示例 css 规则的特异性:
h1 {color: red;} /* 0,0,1 */
body h1 {color: green;} /* 0,0,2 (winner) */
h2.grape {color: purple;} /* 0,1,1 (winner) */
h2 {color: silver;} /* 0,0,1 */
html > body table tr[id="totals"] td ul > li {color: maroon;} /* 0,1,7 */
li#answer {color: navy;} /* 1,0,1 (winner) */
1.1 声明和特异性
一旦选择器的特异性被确定下来之后, 这个特殊性值将被赋予其所包含的每个声明(declaration)。
这里解释一下:
- 一个 css stylesheet 中通常包含一到多个规则(rule).
- 一个规则(rule) 由两部分组成: 选择器(selector) 和 声明块(declaration block).
- 一个声明块(declaration block)包含 key 和 value.
比如我们有如下代码:
h1 {color: silver; background: black;}
为了计算特异性, 用户代理(User Agent) 必须将该规则视为“未分组”的独立规则。 因此,上述代码将变为以下代码:
h1 {color: silver;}
h1 {background: black;}
这两条规则的特异性值都是 0,0,1
.
对于分组选择器 (grouped selector) 也会发生同样的过程, 比如原始代码如下:
h1, h2.section {color: silver; background: black;}
用户代理将其视为以下内容:
h1 {color: silver;} /* 0,0,1 */
h1 {background: black;} /* 0,0,1 */
h2.section {color: silver;} /* 0,1,1 */
h2.section {background: black;} /* 0,1,1 */
接下来看一个例子:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Simple styling of a simple document</title>
<style type="text/css">
html { background-color: white; } /* ignore this */
h1 + p {color: black; font-style: italic;} /* 0,0,2 */
p {color: gray; background: white; font-style: normal;} /* 0,0,1 */
*.callout {color: black; background: silver;} /* 0,1,0 */
</style>
</head>
<body>
<h1>Greetings!</h1>
<p class="callout">It's a fine way to start a day, don't you think?</p>
<p> There are many ways to greet a person, but the words are not as important as the act of greeting itself. </p>
<h1>Salutations!</h1>
<p> There is nothing finer than a hearty welcome from one's neighbor.</p>
<p class="callout">Although a steaming pot of fresh-made jambalaya runs a close second.</p>
</body>
</html>
<p class="callout">It's a fine way to start a day, don't you think?</p>
:
- 上述三条规则都会匹配到这个 p.
- *.callout 规则的特异性值最大,因此其中的 color 和 background 声明都会被应用到 <p>. 相应的定义在 h1 + p 和 p 中的 color 或者 background 对于这个 <p> 都不会生效
- 对于 font-style 值,因为 h1 + p 的特异性值大于 p 的特异性值, 因此 h1 + p 中定义的 font-style 生效。
<p>There are many ways to greet a person, but the words are not as important...</p>
:
- 上述三条规则中仅有第 2条匹配,直接应用它所定义的样式。
<p>There is nothing finer than a hearty welcome from one's neighbor.</p>
:
- 上述三条规则中仅有第 1, 2 条匹配
- 因为 h1 + p 的特异性值大于 p 的特异性值, 因此 h1 + p 中定义的 color, font-style 生效。 p 中相关属性对于这个 <p> 不生效。
- p中定义了 backgroud 属性,而 h1 + p 没有, 因此直接应用 p 中的 backgroud
在任何情况下,用户代理都会确定哪些规则与给定元素匹配,计算所有相关的声明及其特异性,确定哪些规则胜出,然后将胜出的规则应用于元素。这些操作必须在每个元素、选择器和声明上执行。此行为是级联的重要组成部分.
In every case, the user agent determines which rules match a given element, calculates all of the associated declarations and their specificities, determines which rules win out, and then applies the winners to the element to get the styled result. These machinations must be performed on every element, selector, and declaration. This behavior is an important component of the cascade.
1.2 多个匹配
当一个元素与分组选择器中的多个选择器匹配时,将使用最具体的选择器。
例如:
li, /* 0,0,1 */
.quirky, /* 0,1,0 */
#friendly, /* 1,0,0 */
li.happy.happy.happy#friendly { /* 1,3,1 */
color: blue;
}
假定你的 HTML 是这样的:
<li class="happy quirky" id="friendly">This will be blue.</li>
分组选择器中的每个选择器都适用于列表项!应该匹配到哪个选择器? 最具体的那个。因此,在本例中,最具体的是 li.happy.happy.happy#friendly
, 其具体性为 1,3,1
。
你可能注意到,我们在其中一个选择器中重复了三次"happy"类名。这是一个小技巧,可以用于类、属性、伪类,甚至 ID 选择器,以提高特异性值。
1.3 零特异性
通用选择器对特异性没有贡献。它的特异性值是 0,0,0
.(这和没有特异性是不同的)
因此,根据以下两个规则, <div> 中的段落将为黑色,其他元素将为灰色:
div p {color: black;} /* 0,0,2 */
* {color: gray;} /* 0,0,0 */
这意味着,包含通用选择器和其他选择器的选择器的特异性不会因通用选择器的存在而改变。
以下两个规则的特异性值是相同的:
div p /* 0,0,2 */
body * strong /* 0,0,2 */
:where()
伪类也是如此,无论其选择器列表中包含什么选择器。因此,:where(aside#warn, code)
的特异性为 0,0,0
。
组合符,包括 ~
、>
、+
和空格符,完全没有特异性,甚至没有零特异性。因此,它们对选择器的整体特异性没有影响。
1.4 ID 和属性选择器的特异性
需要注意的是,ID 选择器和以 id
属性为目标的属性选择器之间的特异性差异。
例如:
html > body table tr[id="totals"] td ul > li {color: maroon;} /* 0,1,7 */
li#answer {color: navy;} /* 1,0,1 (wins) */
第二条规则中的 ID 选择器(#answer)为选择器的整体特异性贡献了 1,0,0
。然而,在第一条规则中,属性选择器([id="totals"]
) 对整体特异性的贡献为
0,1,0
。
因此,对于以下例子:
#meadow {color: green;} /* 1,0,0 */
*[id="meadow"] {color: red;} /* 0,1,0 */
id 值为 meadow
的元素的颜色将会是 green.
1.5 Importance
有时,一些声明非常重要,以至于它比所有其他考虑都重要。CSS将这些声明称为 "重要声明",并允许你通过在声明的终止分号前插入 !important
标志来标记它们:
p.dark {color: #333 !important; background: white;}
必须正确放置 !important
标志,否则声明可能无效:!important
总是位于声明的末尾,分号之前。
标记为 !important
的声明没有特殊的特异性值,而是与非重要的声明分开考虑。实际上,所有 !important
声明都被分组在一起,并且特异性冲突在该组内得到解决. 同样,所有非重要的声明都被视为一个组,任何冲突都发生在非重要的组内。因此,在任何情况下,如果重要声明和非重要声明发生冲突,则重要声明始终会胜出。
在 CSS 中使用
!important
通常是一种不好的做法,而且很少需要这样做。
2. 继承
继承是一种机制,通过这种机制,某些样式不仅应用于指定元素,还应用于其后代元素。
例如,如果将一种颜色应用于 <h1> 元素,则该颜色将应用于 <h1> 内的所有文本,甚至包括该 <h1> 子元素内的文本:
h1 {color: gray;}
<h1>Meerkat <em>Central</em></h1>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Simple styling of a simple document</title>
<style type="text/css">
html { background-color: white; } /* ignore this */
h1 {color: gray;}
</style>
</head>
<body>
<h1>Meerkat <em>Central</em></h1>
</body>
</html>
继承是 CSS 中非常基础的功能之一,因此,你平时几乎不会注意到它。不过,你仍然应该记住以下几点:
- 许多属性是不可继承的。 这通常是为了避免一些不良后果。 比如,
border
属性就是不可继承的。类似的margin
,padding
,background
都是不可继承的。 - 继承的样式没有任何特异性,也没有零特异性。
关于 "继承的样式没有任何特异性,也没有零特异性。", 考虑以下示例代码:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Simple styling of a simple document</title>
<style type="text/css">
html { background-color: white; } /* ignore this */
* {color: gray;}
h1#page-title {color: black;}
</style>
</head>
<body>
<h1 id="page-title">Meerkat <em>Central</em></h1>
<p>
Welcome to the best place on the web for meerkat information!
</p>
</body>
</html>
对于 <em>
的样式:由于通用选择器的样式会被应用到所有元素,并且它的特异性为0,但是继承来的样式 color:black
没有特异性,因此通用选择器在这里胜出。 因此它的颜色为灰色。
3. 级联
当两条特异性值相同的规则应用于同一个元素时会发生什么?浏览器如何解决冲突?
考虑一下这个例子:
h1 {color: red;}
h1 {color: blue;}
上述两条规则的特异性值都是 0,0,1
, 但是一个 h1 元素不可能同时满足是红色和蓝色, 因此它们必须有一个生效而另一个不生效。
这就需要级联机制来解决了。 CSS 的级联规则如下:
- 查找所有包含与给定元素匹配的选择器的规则.
- 对应用于给定元素的所有声明 (declaration) 按照显示权重(explit weight)进行排序.
- 对适用于给定元素的所有声明按来源(origin)进行排序。有三个基本来源:作者、读者和用户代理。通常情况下,作者的样式(开发者是页面的作者)优先于读者的样式,作者和读者的样式都会覆盖用户代理的默认样式。对于标记为 !important 的规则,情况则相反,用户代理样式会覆盖作者样式,而两者都会覆盖读者样式。
- 对适用于给定元素的所有声明按封装上下文(encapsulation context)进行排序. If a style is assigned via a shadow DOM, for example, it has an encapsulation context for all elements within that same shadow DOM and does not apply to elements outside that shadow DOM. This allows encapsulated styles to override styles that are inherited from outside the shadow DOM.
- 根据所有声明是否为元素附加声明(element attached)进行排序. 通过
style
属性分配的样式是元素附加的。从样式表分配的样式(无论是外部的还是嵌入的)则不是. - 按级联层(cascade layer)对所有声明进行排序. 对于正常权重的样式,在 CSS 中出现得越晚,优先级就越高。没有级联层的样式被视为定义在"默认"级联层,该级联层的优先级高于明确创建的级联层中的样式。对于重要权重(important-weight)样式,级联层在 CSS 中出现得越早,其优先级就越高,并且所有显式创建的层中的重要权重样式都会优先于默认层(无论是否为重要层)中的样式。级联层可以出现在任何来源中。
- 对应用于给定元素的所有声明按照特异性值进行排序. 特异性值较高的元素比特异性较低的元素具有更高的权重.
- 对应用于给定元素的所有声明按照特出现的顺序(order of appearance)进行排序. 声明在样式表或文档中出现得越晚,其权重就越大. 出现在导入样式表中的声明被认为位于导入它们的样式表中的所有声明之前.
接下来我们看一些例子,来理解这里的规则。
3.1 按重要性和来源排序
比如, 两个 css 规则匹配到同一元素,但是其中一个被标记为 !important
, 而另外一个没有标记:
p {color: gray !important;}
<p style="color: black;">Well, <em>hello</em> there!</p>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Simple styling of a simple document</title>
<style type="text/css">
html { background-color: white; } /* ignore this */
p {color: gray !important;}
</style>
</head>
<body>
<p style="color: black;">Well, <em>hello</em> there!</p>
</body>
</html>
这个例子中我们看到, 即使段落的 style 属性设置了颜色, important 规则的优先级更高,因此文字依然是灰色。
如果将 important 添加到 style 属性中,那么 style 属性将会获得更高的权重.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Simple styling of a simple document</title>
<style type="text/css">
html { background-color: white; } /* ignore this */
p {color: gray;}
</style>
</head>
<body>
<p style="color: black !important;">Well, <em>hello</em> there!</p>
</body>
</html>
如果两个规则的重要性是相同的,那么就会考虑它们的来源。
对于普通权重的规则,如果某个元素同时与作者样式和读者样式匹配,则使用作者的样式。
p em {color: black;} /* author's stylesheet */
p em {color: yellow;} /* reader's stylesheet */
这个例子中, 段落内的强调文字将会显示为黑色.
对于标记为 important 的规则, 如果某个元素同时与作者样式和读者样式匹配,则使用读者的样式。
p em {color: black !important;} /* author's stylesheet */
p em {color: yellow !important;} /* reader's stylesheet */
这个例子中, 段落内的强调文字将会显示为黄色.
事实上,用户代理的默认样式(通常受用户偏好的影响)也会被考虑。默认样式声明的影响是最小的。因此,如果作者定义的规则适用于锚点(例如,将其声明为白色),则该规则将覆盖用户代理的默认样式。
总而言之,CSS 在声明优先级方面有8个基本级别需要考虑。按优先级从高到低的顺序排列如下:
- Transition declarations
- 用户代理重要声明 (User agent important declaration)
- 读者重要声明 (Reader important declaration)
- 作者重要声明 (Author important declaration)
- Animation declarations
- 作者的普通声明 (Author normal declaration)
- 读者的普通声明 (Reader normal declaration)
- 用户代理的声明 (User agent declaration)
3.2 按元素附加排序
可以使用 style 属性将样式附加到元素。这些被称为元素附加样式,其权重仅次于来源和权重。
比如,以下代码:
h1 {color: red;}
<h1 style="color: green;">The Meadow Party</h1>
<h1> 的文本为绿色。这是因为每个内联声明都是附加在元素上的,因此其权重高于未附加元素的样式(例如 color: red 规则)。
3.3 按级联层排序
级联层允许开发者将样式分组,以便它们在级联中有相同的优先级。
如果某个元素存在冲突的声明,且所有声明都具有相同的显式权重和来源,且均未附加元素,则接下来按级联层对它们进行排序。
级联层的优先级顺序由级联层首次声明或使用的顺序决定,对于普通级联层样式,后声明的级联层优先于先声明的级联层。
例如:
@layer site {
h1 {color: red;}
}
@layer page {
h1 {color: blue;}
}
此例中, <h1> 的颜色将会是蓝色. 因为 page layer 出现在 site layer 之后.
不属于命名级联层的样式会被分配到一个隐式的 "默认" 层,对于不重要的规则,该层比任何命名层都具有更高的优先级。
比如:
h1 {color: maroon;}
@layer site {
h1 {color: red;}
}
@layer page {
h1 {color: blue;}
}
此例中, <h1> 的颜色将会是栗色. 因为隐式的 "默认" 层的优先级更高。
可以为命名级联层定义特定的优先顺序。 像这样:
@layer site, page;
@layer page {
h1 {color: blue;}
}
@layer site {
h1 {color: red;}
}
这里,第一行定义了各层的优先级:对于普通权重规则,page layer 的优先级将高于 site layer。
对于标记为重要的规则,优先顺序是相反的。
3.4 按特异性值排序
如果某个元素存在冲突的声明,且这些声明都具有相同的显式权重、来源、元素附加和级联层,则它们将按特异性排序。
@layer page {
p#bright#bright#bright {color: grey;} /* 3,0,1 */
}
p#bright {color: silver;} /* 1,0,1 */
p {color: black;} /* 0,0,1 */
<p id="bright">Well, hello there!</p>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Simple styling of a simple document</title>
<style type="text/css">
html { background-color: white; } /* ignore this */
@layer page {
p#bright#bright#bright {color: grey;} /* 3,0,1 */
}
p#bright {color: silver;} /* 1,0,1 */
p {color: black;} /* 0,0,1 */
</style>
</head>
<body>
<p id="bright">Well, hello there!</p>
</body>
</html>
我们看到最终的 p 的颜色是银色。 p#bright
特异性大于 p
. 虽然 p#bright#bright#bright
特异性更大,但是它处于一个优先级较低的级联曾,这里直接排除他。
3.5 按顺序排序
最后,如果两个规则具有完全相同的显式权重、来源、元素附件、级联层和特异性,则样式表中较晚出现的规则将胜出。
例如:
body h1 {color: red;}
html h1 {color: blue;}
此例中,h1 的颜色将会是蓝色,因为 html h1
出现的顺序更靠后。