CSS 优先级规则概览
CSS 优先级有着明显的不可逾越的等级制度,我将其划分为 0~5 这 6 个等级,其中前 4 个等级由 CSS 选择器决定,后 2 个等级由书写形式和特定语法决定。下面我将对这 6 个等级分别进行讲解。
0 级
通配选择器、选择符和逻辑组合伪类的优先级都是 0。
通配选择器写作星号*
。示例如下:
* { color: #000; }
选择符指+
、>
、~
、空格和||
。
逻辑组合伪类有:not()
、:is()
和:where
等,这些伪类本身并不影响 CSS 优先级,影响优先级的是括号里面的选择器。例如:
:not() {}
需要注意的是,只有逻辑组合伪类的优先级是 0,其他伪类的优先级并不是这样的。
1 级
标签选择器的优先级是 1。示例如下:
body { color: #333; }
2 级
类选择器、属性选择器和伪类的优先级是 2。示例如下:
.foo { color: #666; }
[foo] { color: #666; }
:hover { color: #333; }
3 级
ID 选择器的优先级是 3。示例如下:
#foo { color: #999; }
4 级
style
属性内联样式的优先级是 4。示例如下:
<span style="color: #ccc;">http://c.biancheng.net/</span>
5级
!important
优先级是最高的,也就是 5。示例如下:
.foo { color: #fff !important; }
!important
是顶级优先级,可以重置 JavaScript 设置的样式,唯一推荐使用的场景就是使 JavaScript 设置无效。例如:
.foo[style*="color: #ccc"] {
color: #fff !important;
}
对于其他场景,没有任何使用它的理由,切勿滥用。
不难看出,CSS 选择器的优先级(0 级至 3 级)属于 CSS 优先级的一部分,也是最重要、最复杂的部分,学会 CSS 选择器的优先级等同于学会了完整的 CSS 优先级规则。
CSS 选择器优先级的计算规则
对于 CSS 选择器优先级的计算,业界流传甚广的是数值计数法。具体如下:每一段 CSS 语句的选择器都可以对应一个具体的数值,数值越大优先级越高,其中的 CSS 语句将被优先渲染。具体规则为:
-
出现一个 0 级选择器,优先级数值
+0
; -
出现一个 1 级选择器,优先级数值
+1
; -
出现一个 2 级选择器,优先级数值
+10
; -
出现一个 3 级选择器,优先级数值
+100
。
于是,有下表所示的计算结果。
选择器 |
计算值 |
计算细则 |
---|---|---|
* {} |
0 |
1 个 0 级通配选择器,优先级数值为 0 |
dialog {} |
1 |
1 个 1 级标签选择器,优先级数值为 1 |
ul > li {} |
2 |
2 个 1 级标签选择器,1 个 0 级选择符,优先级数值为 1+0+1 |
li > ol + ol {} |
3 |
3 个 1 级标签选择器,2 个 0 级选择符,优先级数值为 1+0+1+0+1 |
.foo {} |
10 |
1 个 2 级类名选择器,优先级数值为 10 |
a:not([rel=nofollow]) {} |
11 |
1 个 1 级标签选择器,1 个 0 级否定伪类,1 个 2 级属性选择器,优先级数值为 1+0+10 |
a:hover {} |
11 |
1 个 1 级标签选择器,1 个 2 级伪类,优先级数值为 1+10 |
ol li.foo {} |
12 |
1 个 2 级类名选择器,2 个 1 级标签选择器,1 个 0 级空格选择符,优先级数值为 1+0+1+10 |
li.foo.bar {} |
21 |
2 个 2 级类名选择器,1 个 1 级标签选择器,优先级数值为 10×2+1 |
#foo {} |
100 |
1 个 3 级 ID 选择器,优先级数值为 100 |
#foo .bar p {} |
111 |
1 个 3 级 ID 选择器,1 个 2 级类名选择器,1 个 1 级标签选择器,优先级数值为 100+10+11 |
测试题
趁热打铁,我出一个小题考考大家,<body> 元素的颜色是红色还是蓝色?
HTML 代码:
<html lang="zh-CN"> <body class="foo">颜色是?</body> </html>
CSS 代码:
body.foo:not([dir]) { color: red; } html[lang] > .foo { color: blue; }
我们先来计算一下各自的优先级数值。
首先是body.foo:not([dir])
,出现了 1 个标签选择器 body,1 个类名选择器.foo
和 1 个否定伪类:not
,以及属性选择器[dir]
,计算结果是 1+10+0+10,也就是 21。
接下来是html[lang] > body.foo
,出现了 1 个标签选择器 html,1 个属性选择器[lang]
和 1 个类名选择器.foo
,计算结果是 1+10+10,也就是 21。
这两个选择器的计算值居然是一样的,那该怎么渲染呢?
这就引出了另外一个重要的规则——“后来居上”。也就是说,当 CSS 选择器的优先级数值一样的时候,后渲染的选择器的优先级更高。因此,上题的最终颜色是蓝色(blue)。
后渲染优先级更高的规则是相对于整个页面文档而言的,而不仅仅是在一个单独的 CSS 文件中。例如:
<style>body { color: red; }</style>
<link rel="stylesheet" href="a.css">
<link rel="stylesheet" href="b.css">
其中在 a.css 中有:
body { color: yellow; }
在 b.css 中有:
body { color: blue; }
此时,body 的颜色是蓝色,如下图所示,因为 blue 这段 CSS 语句在文档中是最后出现的。
图1:浏览器中 body 颜色的优先级
还有一个误区有必要强调一下,那就是 CSS 选择器的优先级与 DOM 元素的层级位置没有任何关系。例如:
body .foo { color: red; }
html .foo { color: blue; }
请问.foo
的颜色是红色还是蓝色?
答案是蓝色。虽然 <body> 是 <html> 的子元素,离.foo
的距离更近,但是选择器的优先级并不考虑 DOM 的位置,所以后面的html.foo{}
的优先级更高。
增加 CSS 选择器优先级的小技巧
实际开发时,难免会遇到需要增加 CSS 选择器优先级的场景。例如,希望增加下面.foo
类名选择器的权重:
.foo { color: #333; }
很多人的做法是增加嵌套,例如:
.father .foo {}
或者是增加一个标签选择器,例如:
div.foo {}
但这些都不是最好的方法,因为这些方法增加了耦合,降低了可维护性,一旦哪天父元素类名变化了,或者标签换了,样式岂不是就失效了?
这里给大家介绍一个增加 CSS 选择器优先级的小技巧,那就是重复选择器自身。例如,可以像下面这样做,既提高了优先级,又不会增加耦合,实在是上上之选:
.foo.foo {}
如果你实在不喜欢这种写法,借助必然会存在的属性选择器也是不错的方法。例如:
.foo[class] {}
#foo[id] {}
对数值计数法的点评
上面提到的 CSS 选择器优先级数值的计数法实际上是一个不严谨的方法,因为 1 和 10 之间的差距实在太小了,这也就意味着连续 10 个标签选择器的优先级就和 1 个类名选择器齐平了。
然而事实并非如此,不同等级的选择器之间的差距是无法跨越的存在。但由于在实际开发中,我们是不会连续写上多达 10 个选择器的,因此不会影响我们在实际开发过程中计算选择器优先级。
而且对于使用 CSS 选择器而言,你的书写习惯远比知识更重要,就算你理论知识再扎实,如果平时书写习惯糟糕,也无法避免 CSS 样式覆盖问题、样式冲突等问题的出现。因此,对于数值计算法,我的态度是,学一遍即可,没有必要反复攻读,做到面面俱到,只要你习惯足够好,是不会遇到乱七八糟的优先级问题的。
在 CSS 选择器这里,等级真的是无法跨越的鸿沟吗?其实不是,这里有大家不知道的冷知识。
256 个选择器的越级现象
有如下 HTML:
<span id="foo" class="f">颜色是?</span>
如下CSS:
#foo { color: #000; background: #eee; }
.f { color: #fff; background: #333; }
很显然,文字的颜色是#000
,即黑色,因为 ID 选择器的级别比类名选择器的级别高一级。但是,如果是下面的 CSS呢?256个.f
类名合体:
#foo { padding: 10px 20px; color: #000; background: #eee; }
.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f.f { color: #fff; background: #333; }
在 IE 浏览器下,神奇的事情发生了,文字的颜色表现为白色,背景色表现为深色,如下图所示。
图2:IE 浏览器中类名的优先级更高
同样,256 个标签选择器的优先级大于类名选择器的优先级的现象也是存在的。
实际上,在过去,Chrome 浏览器、Firefox 浏览器下都出现过这种 256 个选择器的优先级大于上一个选择器级别的现象,后来,大约 2015 年之后,Chrome 浏览器和 Firefox 浏览器都修改了策略,使得再多的选择器的优先级也无法超过上一级,因此,目前越级现象仅在 IE 浏览器中可见。
为什么会有这种有趣的现象呢?早些年查看 Firefox 浏览器的源代码,发现所有的类名都是以 8 字节字符串存储的,8 字节所能容纳的最大值就是 255,因此同时出现 256 个类名的时候, 势必会越过其边缘,溢出到 ID 区域。而现在采用了 16 字节的字符串存储,能容纳的类型数量足够多了,就不会出现这种现象。
当然,这个冷知识并没有多大的实用价值,大致了解一下即可。
为什么按钮:hover
变色了
了解了 CSS 选择器的优先级之后,很多日常工作中遇到的一些问题你就知道是怎么回事了,举一个按钮:hover
变色的例子。
例如,我们写一个蓝底白字的按钮,使鼠标经过按钮时会改变背景色。
HTML 代码:
<a href="http://c.biancheng.net/python/" class="cs-button" role="button">Python教程</a>
CSS 代码:
.cs-button { background-color: darkblue; color: white; } .cs-button:hover { background-color: blue; }
看代码没有任何问题,但是页面一刷新就出现问题了。鼠标经过按钮的时候,文字居然变成蓝色了,而不是预期的白色!
究竟是哪里出了问题呢?一排查,这个问题居然是 CSS reset 导致的。
在实际开发中,我们一定会对全局的链接颜色进行设置,例如,按钮默认颜色为蓝色,鼠标经过的时候变成深蓝色:
a { color: blue; }
a:hover { color: darkblue; }
按钮变色就是这里的a:hover
导致的。因为a:hover
的优先级比.cs-button
的优先级高(:hover
伪类的优先级和类选择器的优先级一样),所以鼠标经过按钮的时候按钮颜色表现为a:hover
设置的深蓝色。
知道原因,问题就好解决了,常见做法是再设置一遍鼠标经过按钮的颜色:
.cs-button:hover { color: white; background-color: blue; }
或者按钮改用语义更好的 button 标签,而不是传统的 a 标签。
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/23612.html