第2章里面我们了解了document的结构和CSS选择器是如何查找定位对应的元素的。每一个合理的document都会生成一个结构树,基于此,选择器基于元素的祖先,属性,兄弟节点和更多的因素来定位元素。而且这一dom结构树也是CSS种实现继承的基础。
继承是(Inheritance)CSS属性如何从一个元素传递到它的所有子元素的过程。浏览器在决定给一个元素配置什么样式的时候,除了要考虑继承关系,同时也要考虑元素的特异性(Specificity,这个翻译需要斟酌下,但是意思很明显,存在于多个冲突样式中的优先级机制)。而考虑的这一过程就称为级联(cascade)。接下来我们就来探讨一下这三种机制-特异性,继承和级联。后两者的区别其实可以简单归纳为:h1{color:red;color: blue;}的结果就是级联,而在h1中创建一个span就是继承。
综上,别管这几个有多抽象,继续学习,会看到回报的。
1.特异性(Specificity)
从第二章我们看到选组元素的方式太多了,所有很有可能同一个元素被很多规则选中,来看看下面的例子:
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;}
上面的3对样式中每对都是作用在同一个元素上,那么到底浏览器该在元素上应用哪一个样式呢?结果就在每个选择规则的特异性中。浏览器逐条评估每条选择器规则的特异性,然后将样式声明应用到元素上。当同一个元素中包含多个存在冲突的属性声明时,有最高特异性的属性就会胜出,把其他冲突规则替换掉。
一个选择器的特异性有选择器的组成决定,而一个特异性的值可以分成4部分,就像这样:0,0,0,0。而真实的特异性是由下面这些决定的:
- 针对每个有ID属性的值,特异性值增加0,1,0,0
- 针对每个类属性的值,属性选择或是伪类选择,特异性值增加0,0,1,0
- 针对每个元素或是伪元素,添加0,0,0,1。其实在CSS2.0的时候对于是否针对伪元素增加特异性值还是矛盾的,不过到了CSS2.1他们确定要加了。
- 任何的链接符号和通用选择器对特异性值没有任何贡献
还是举例来说明吧,分别来看看不同的选择规则对应的特异性值:
h1 {color: red;}/* specificity = 0,0,0,1 */
p em {color: purple;}/* specificity = 0,0,0,2 */
.grape {color: purple;}/* specificity = 0,0,1,0 */
*.bright {color: yellow;}/* specificity = 0,0,1,0 */
p.bright em.dark {color: maroon;}/* specificity = 0,0,2,2 */
#id216 {color: blue;}/* specificity = 0,1,0,0 */
div#sidebar *[href] {color: silver;} /* specificity = 0,1,1,1 */
既然刚学了如果计算特异性值,赶紧用在之前的例子上吧:
h1 {color: red;} /* 0,0,0,1 */
body h1 {color: green;} /* 0,0,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) */
1.1 声明和特异性
虽然我们一般在写CSS的时候会把多个属性都写在一起,例如:
h1 {color: silver; background: black;}
然而实际上浏览器为了更好的计算特异性值实际会把聚合的属性进行单独的拆分:
h1 {color: silver;}
h1 {background: black;}
咱们再来考虑一个稍微复杂的例子:
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 */
<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 nothing 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>
基于这个结构最终显示的效果是这样的:
从这个例子中,我们可以很好的看到浏览器按照之前说的特异性的规则展示对应不同值得样式结果。可以看到,浏览器要对每个元素进行拆分,然后计算特异性值后再确定对这个元素渲染那些样式,想想整个流程就很繁杂,幸好浏览器都自动帮我们做好了,而且这是我们后面讲到的级联的很重要的一环。同样,详细了解浏览器的渲染流程也对我们优美的书写CSS样式有很大助益。
1.2 通用选择器特异性
通用选对特异性值是没有贡献的,但它是有值的,虽然值是0,0,0,0,但这和没有特异性值是有区别的,后面会在继承章节中详细讨论。所以下面的结果是显而易见的:
div p {color: black;} /* 0,0,0,2 */
* {color: gray;} /* 0,0,0,0 */
除了div的后代p显示为黑色外,其他都显示为灰色。而通用选择器是不贡献特异性值得,因此下面两个表达式的值也是一样的:
div p /* 0,0,0,2 */
body * strong /* 0,0,0,2 */
连接符号是没有特异性值的,连0都没有,所有对选择器的值没有贡献。
1.3 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 (wins) */
因为直接用ID选择器贡献的特异性值为0,1,0,0,而属性贡献的值为0,0,1,0, 所以下面的结果也是显而易见的:
#meadow {color: green;} /* 0,1,0,0 */
*[id="meadow"] {color: red;} /* 0,0,1,0 */
1.4 内联样式特异性
到现在肯定很多读者会想,之前计算的特异性值的4位里面第一位是什么时候设置的?答案就是为内联样式设计的,比其他的所有声明优先级都高。来看看下面的例子:
h1 {color: red;}
<h1 style="color: green;">The Meadow Party</h1>
结果很明显h1就是绿的,因为内联样式的值更高,或者说优先级更高。
1.5 重要性
有时候特异性值更高的样式还并不是你想要的,这时候可使用!important来表示那些特别重要的样式,不过格式有严格的规定,就是必须在分号前面加,例如:
p.dark {color: #333 !important; background: white;}
!important表达式必须正确,不然的话会报错。那如果存在对同一个样式有多个important声明会怎样呢?那么这些样式都不会被应用,所以需要保证important的唯一性。
其实浏览器是这么处理important样式和非important样式的:将它们单独分成两拨,important的一拨优先级更高,并且在这个组内处理自身的冲突啥的;同样的逻辑对应于非important组,组内采用特异性值来解决冲突。来看个例子:
h1 {font-style: italic; color: gray !important;}
.title {color: black; background: silver;}
* {background: black !important;}
<h1 class="title">NightWing</h1>
2. 继承
继承,顾名思义就是属性在祖先-后代节点间的传递机制。比如说一个在h1标签中设定的颜色,那么h1中的所有后代文本都会继承。
h1 {color: gray;}
<h1>Meerkat <em>Central</em></h1>
上例中em就是继承了h1中的gray颜色。来看个ul标签的例子:
ul {color: gray;}
可以看到ul的所有li元素也都继承了这一特性。继承看似简单不过也需要注意几点:
-
记住有些属性是不能被继承的,例如border属性,就像下面这张,我敢说,用户是不太想要让段落下面的所有链接都继承类似margin-left:30px这种样式的,所以不只是border,大多数的盒模型属性(包括margin,padding,background和border)都是不继承的。
- 继承的属性是没有特异性值的,连0,0,0,0都不是。来看下面这个例子:
* {color: gray;}
h1#page-title {color: black;}
<h1 id="page-title">Meerkat <em>Central</em></h1>
<p>Welcome to the best place on the web for meerkat information! </p>
有没有很意外em中的元素为啥还是灰的,照理说应该都是黑色才对,就是因为继承来的黑色属性没有特异性值,而被全局的样式给替换了。
有时候没有特异性值也会带来麻烦,比如看:
#toolbar {color: white; background: black;}
如果id=toolbar的元素下面有链接,那么就很有可能因为没有定义而采用浏览器的默认样式,一般来说是这样:
a:link {color: blue;}
解决方案归根结底还是要有特异性值,可以有多种方法都是可以的:
#toolbar a:link {color: white;}
#toolbar a:link {color: inherit;}
3. 层级级联
再来考虑下个问题,如果两个具有完全相同特异性值的元素作用在同一个节点上,那该怎么办?比如说像这样:
h1 {color: red;}
h1 {color: blue;}
还记得css为啥叫css么?Cascading Style Sheet,级联样式表,通过定义相关的规则来结合继承和特异性值。来看看都有哪些吧。
- 找出匹配元素对应的所有规则;
- 将所有的声明按照特定权重来排序,当然啦,!important的优先级更高点;
- 元素按照源头来将所有声明排序,这里的源头有3个:作者,读者和user agent(浏览器)。正常情况下,作者的样式高于读者样式,important的读者样式比其他都高,包括important的作者样式,而且两者都会覆盖浏览器的默认样式。
4.将元素按照特异性值来排序,数值高者优先; - 将元素按照出现顺序排序,越靠后的越优先。
为了更好理解,我们对后四个规则进行详细说明:
3.1 按照源头权重排序
p {color: gray !important;}
<p style="color: black;">Well, <em>hello</em> there!</p>
虽然p本身有内联的样式,但因为标记了!important,所以还是显示为灰色。另外即使给内联样式加上了important也没啥用:
p {color: gray !important;}
<p style="color: black !important;">Well, <em>hello</em> there!</p>
在上面权重相同的情况下对比源头,作者的优先级高于读者的。但如果在important情况下,读者的优先级要高于作者的,总结一下顺序就是:
- 读者-important
- 作者-important
- 作者
- 读者
- 浏览器
3.2 按照特异性值排序
如果上面的类型相同,则会进入到这一步,判断特异性值。
3.3 按照出现顺序排序
如果特异性值也一致,最后判断下出现的顺序,后出来的覆盖先前的。
h1 {color: red;}
h1 {color: blue;}
结果就是blue颜色。而这一顺序对import进来的样式也是一样的。
@import url(basic.css);
h1 {color: blue;}
而就是因为这个原因才会有链接推荐样式的顺序方案,一般称为LVFHA,就像下面这样:
a:link {color: blue;}
a:visited {color: purple;}
a:focus {color: green;}
a:hover {color: red;}
a:active {color: orange;}
这个顺序还可以有另一种玩法,实现的效果是只有未访问的链接才会有hover样式,而且所有链接都会有active样式,就像下面这样:
a:link {color: blue;}
a:hover {color: red;}
a:visited {color: purple;}
a:focus {color: green;}
a:active {color: orange;}
不过最好的办法就是使用串联的伪类来彻底消除这种情况,为不同的状态设定不同的样式:
a:link {color: blue;}
a:visited {color: purple;}
a:link:hover {color: red;}
a:visited:hover {color: gray;}
不过如果引入了active类的话还是会触发冲突:
a:link {color: blue;}
a:visited {color: purple;}
a:link:hover {color: red;}
a:visited:hover {color: gray;}
a:link:active {color: orange;}
a:visited:active {color: silver;}
如果我们把最后两个active状态移到hover前面,那么它们就会被忽略,当然也是有办法解决的,就是引入更多的伪类:
a:link:hover:active {color: orange;}
a:visited:hover:active {color: silver;}
4. 总结
可能对CSS来说最基本的就是本章所讲的级联机制本身,如何解决冲突,如何实现继承,如何实现排序,都有一套底层的规则在作用。