网页的结构和重叠

前面我们介绍了如何利用网页文档结构和CSS选择器为元素应用各种丰富的样式。每个合法的文档都会生成一个结构树,了解了这一点,就能根据元素的祖先、属性、兄弟元素等等创建选择器来选择元素。有了这个结构树,选择器才能起作用,这也是CSS另一个重要方面(即继承)的核心。

继承(Inheritance)是从一个元素向其后代元素传递属性值所采用的机制。确定应当向一个元素应用哪些值时,网页浏览器不仅要考虑继承,还要考虑声明的特殊性,另外需要考虑声明本身的来源。这个过程就称为层叠(cascade)。我们接下来将讨论这3种机制之间的关联:特殊性、继承和层叠。

特殊性

我们现在知道了可以使用多种不同的方法选择元素。实际上,可能同一个元素可以使用两个或多个规则来选择,每个规则都有其自己的选择器,下面考虑以下3对规则。假设每一对规则都匹配同样的元素:

h1 {color: red;}

body h1 {color: green;}

h2.grape {color: purple;}

h2 {color: silver;}

html > body table tr[id="totals"] td ul > li {color: maroon;}

li#answer {color: navy;}

显然,每一对规则中只有一个胜出,因为所匹配的元素只能是某一种颜色(或此或彼)。那么怎么知道哪一个规则更强呢?

答案就在于每个选择器的特殊性(specificity)。对于每个规则,用户代理会计算选择器的特殊性,并将这个特殊性附加到规则中的各个声明。如果一个元素有两个或多个冲突的属性声明,那么有最高特殊性的声明就会胜出。

注意:这并不是解决冲突的全部。实际上,所有样式冲突的解决都由层叠来处理,本章后面专设了一节介绍这个内容。

选择器的特殊性由选择器本身的组件确定。特殊性值表述为4个部分,如:0,0,0,0。一个选择器的具体特殊性如下确定:

对于选择器中给定的各个ID属性值,加0,1,0,0。

对于选择器中给定的各个类属性值,属性选择或伪类,加0,0,1,0。

对于选择器中给定的各个元素和伪元素,加0,0,0,1。伪元素是否有特殊性,在这方面css2有些自相矛盾,不过css2.1很清楚的指出,伪元素有特殊性,而且其特殊性为0,0,0,1。

结合符和通配符选择器对特殊性没有任何贡献(后面还会更多地介绍这些值)。

例如,以下规则中选择器的特殊性见注释:

h1{color:red;}/* specifity = 0,0,0,1 */

p em{color:purple;}/* specifity = 0,0,0,2 */

.grape{color:purple;}/* specifity = 0,0,1,0 */

*.bright{color:yellow;}/* specifity = 0,0,1,0 */

p.bright em.dark{color:maroon;}/* specifity = 0,0,2,2 */

#id216{color:blue;}/* specifity = 0,1,0,0 */

div#sidebar *[href]{color:silver;}/* specifity = 0,1,1,1 */

假设有以下情况,一个em元素与上例中的第2条规则匹配,又与第5条规则匹配,这个元素将是紫红色,因为第5条规则的特殊性高于第2条规则的特殊性。

下面做个练习,回顾本节前面给出的几组规则,看看它们有怎样的特殊性:

h1 {color:red;}/* 0,0,0,1 */

body h1 {color:green;}/* 0,1,0,2 (winner)*/

h2.grape{color:purple;}/* 0,0,1,1 (winner)*/

h2 {color:silver;}/* 0,0,0,1 */

html > body table tr[id="totals"] td ul > li {color: maroon;}/* 0,0,1,7 */

li#answer {color: navy;}/* 0,1,0,1 (winner)*/

上面已经指出每组规则中的胜出规则,在上述各种情况下,那些规则之所以胜出是因为其特殊性更高。要注意特殊性是如何排序的。在第二组中,选择器h2.grape能“贏”是因为它多了一个1: 0,0,1,1大于0,0,0,1。在第三组中,第二个规则胜出是因为0,1, 0,1大于0,0,1,7。实际上,特殊性值0,0,1,0比值0,0, 0,13更高。

之所以会这样,是因为值是从左向右排序的。特殊性值1,0, 0,0大于以0开头的所有特殊性值,而不论后面的数是什么。所以0,1, 0,1比0,0,1,7高,因为前一个值中第二位上的1大于第二个值中第二位上的0。

声明和特殊性

一旦确定一个选择器的特殊性,这个值将授予其所有相关声明。考虑以下规则:

h1 {color: silver; background: black;}

由于特殊性的缘故,网页浏览器必须相应地处理这个规则,将其“解组”为单独的规则。因此,前面的例子将变成:

h1 {color: silver;}

h1 {background: black;}

这两个规则的特殊性都是0, 0 , 0 , 1,各声明得到的特殊性值也就是0, 0 , 0 , 1。分组选择器也同样会完成这种分解过程。给定以下规则:

h1, h2.section {color: silver; background: black;}

网页浏览器将把它处理为:

h1 {color: silver;}/* 0,0,0,1 */

h1 {background: black;}/* 0,0,0,1 */

h2.section {color: silver;}/* 0,0,1,1 */

h2.section {background: black;}/* 0,0,1,1 */

如果多个规则与同一个元素匹配,而且有些声明相互冲突,在这种情况下特殊性就很重要。例如,考虑以下规则:

h1 + p {color: black; font-style: italic;}/* 0,0,0,2 */

p {color: gray; background: white; font-style: normal;}/* 0,0,0,1 */

*.aside {color: black; background: silver;}/* 0,0,1,0 */

当这些规则应用到以下标记时,显示的内容将如图3-1所示:

<h1>Greetings!</h1>

<p class="aside"> 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 noth1ng finer than a hearty welcome from one's fellow man.

</p>

<p class="aside"> Although a thick and juicy hamburger with bacon and mushrooms runs a close second.

</p>

任何情况下,用户代理都会确定哪些规则与一个元素匹配,计算出所有相关的声明及其特殊性,确定哪些规则胜出,然后将胜出的规则应用到元素,从而得到应用样式后的结果。每个元素、选择器和声明上都必须完成这些工作。幸运的是,用户代理会自动完成所有这些工作。这个行为是层叠的一个重要部分,本章后面还将深入讨论层叠。

通配选择器特殊性

前面提到过,通配选择器对一个选择器的特殊性没有贡献。换句话说,其特殊性为0,0,0,0,这与根本没有特殊性有区别(有关内容将在“继承”一节中介绍)。因此,给定以下两条规则,div下包含的段落将是黑色,而其他元素都是灰色:

div p {color: black;}/* 0,0,0,2 */

*{color: gray;}/* 0,0,0,0 */

如你所料,这意味着如果一个选择器中包含通配选择器和其他选择器,该选择器的特殊性不会因通配选择器的出现而改变。下面两个选择器的特殊性完全相同:

div p /* 0,0,0,2 */

body * strong /* 0,0,0,2 */

相比之下,结合符则根本没有特殊性,甚至连0特殊性都没有。因此,它们对选择器的总特殊性没有任何影响。

ID和属性选择器的特殊性

需要着重指出,ID选择器和指定id属性的属性选择器在特殊性上有所不同。再来看示例代码中的第三组规则,可以看到:

html > body table tr[id="totals"] td ul > li {color: maroon;}/* 0,0,1,7 */

li#answer {color: navy;}/* 0,1,0,1 {winner}*/

第二个规则中的ID选择器(#answer)为选择器的总特殊性贡献了0,1, 0,0。而在第一个规则中,属性选择器([id="totals"])只对总特殊性贡献了0,0,1,0。因此,给定以下规则,id为meadow的元素将变成绿色:


#meadow {color: green;}/* 0,1,0,0 */

*[id:"meadow"]{color: red;}/* 0,0,1,0 */

内联样式特殊性

到目前为止,我们已经见过以0开头的特殊性,所以你可能会奇怪为什么会有这些特殊性。一般地,第一个0是为内联样式声明保留的,它比所有其他声明的特殊性都高。考虑以下规则和标记片段:

h1 {color: red;}

<h1 style="color: green?">The Meadow Party</h1>

假设这个规则应用到h1元素,h1的文本还将是绿色。CSS2.1中就是如此,这是因为每个内联声明的特殊性都是1,0, 0,0。

这意味着,即使有id属性的元素与某个规则匹配,也必须遵循内联样式声明。下面把上例修改为包括一个id属性:

h1#meadow {color: red;}

<h1 id="meadow" style="color: green;">The Meadow Party</h1>

由于内联声明的特殊性最高,h1元素的文本还是绿色。

注意:为内联样式声明保留一位,这是CSS2.1才新增的,这样做是为了反映写CSS2.1当时的Web 浏览器表现。在CSS2中,内联样式声明的特殊性是1,0,0 (CSS2特殊性包含3个值,而不是4个)。换句话说,它与ID选择器的特殊性相同,所以ID选择器很容易覆盖内联样式。

重要性

有时某个声明可能非常重要,超过了所有其他声明。CSS2.1称之为重要声明(原因显而易见),并允许在这些声明的结束分号之前插入!important来标志。

p.dark {color:#333 !important; background: white;}

在此为颜色值#333加了标志!important,而背景值white未加这个标志。如果你希望把两个声明都标志为重要,那么每个声明都需要它自己的!important标志:

p.dark {color:#333 !important; background: white !important;}

必须正确地放置!important,否则声明将无效。! important总是放在声明的最后,即分号前面。如果一个属性的值可以包含多个关键词,如font,这一点则尤其重要,必须将!important标志放在声明的最后:

p.light {color: yellow; font: smaller Times, serif !important;}

如果!important放在font声明的任何其他位置,整个声明都将无效,相应地不会应用其任何样式。

标志为!important的声明并没有特殊的特殊性值,不过要与非重要声明分开考虑。实际上,所有!important声明会分组在一起,重要声明的特殊性冲突会在重要声明内部解决,而不会与非重要声明相混。类似地,我们认为所有非重要声明也归为一组,使用特殊性来解决冲突。如果一个重要声明和一个非重要声明冲突,胜出的总是重要声明。图3-2展示了以下规则和标记片段的结果:

h1 {font-style: italic; color: gray !important;}

.title {color: black; background: silver;}

*{background: black !important;}

<h1 class="title">NightWing</h1>