在学习 CSS
的过程当中,我们可能听说过特指度(I-C-E
)这个东西,简单来说特指度表示一个 CSS
选择器表达式的重要程度,通常称为 I-C-E
计算公式,其中的 I
对应着 Id
,C
对应着 class
,E
对应着 Element
,在针对一个 CSS
选择器表达式的时候,遇到一个 Id
就往特指度数值中加 100
,遇到一个 class
就往特指度数值中加 10
,遇到一个 Element
就往特指度数值中加 1
但是上面的这种计算方式是存在一定问题的,虽然在大多数情况下,按照这样的理解得出的结论是没有问题的,但是在一些比较特殊的情况之下还是存在问题的,所以今天就抽了点时间,让我们一起来深入的了解一下 CSS
当中的样式层叠权重值,既然是样式层叠权重值,那么我们就先来看看什么是权重
CSS 权重
CSS
权重,我们可以简单理解为 CSS
优先级,下面是 MDN
对优先级的描述
浏览器通过优先级来判断哪一些属性值与一个元素最为相关,从而在该元素上应用这些属性值,优先级是基于不同种类选择器组成的匹配规则
我们通过一个小例子来加深理解
1 | <p class="text" id="text">我是一段简单的文本。</p> |
1 | .text { |
运行后可以发现,p
标签文本的颜色为蓝色,那么为什么会是蓝色呢?这就是 CSS
权重来决定的,它决定最终用哪个样式作用到 p
标签上,在网上流传的说法当中,关于选择器各级别的优先级,大家应该已经很清楚了,常见的说法是下面这样的
1 | !important > 内联 > ID > 类 > 标签/伪类/属性选择 > 伪对象 > 通配符 > 继承 |
那么这个顺序是怎么得出来的呢?
实际上在 CSS 2.1 规范当中关于 具体性(specificity) 的定义当中,描述是非常明确的,根据 CSS
规范,具体性越明确的样式规则,权重值越高,而计算权重值的依据,并不像是我们在开头部分所描述的 class
是 10
,标签是 1
,Id
是 100
之类的,虽然这样在大多数情况下能够得到正确的结果
选择器权重值的计算
下面我们就根据 CSS
规范,来具体的看一下选择器权重值到底是如何进行计算的,主要有以下四点
A
,如果规则是写在标签的style
属性中(内联样式),则A = 1
,否则A = 0
,对于内联样式,由于没有选择器,所以B
、C
、D
的值都为0
,即A = 1
,B = 0
,C = 0
,D = 0
(简写为1, 0, 0, 0
)B
,计算该选择器中Id
的数量(例如#header
这样的选择器计算为0, 1, 0, 0
)C
,计算该选择器中伪类及其它属性的数量,包括类选择器、属性选择器等,不包括伪元素(例如.logo[id='site-logo']
这样的选择器,计算为0, 0, 2, 0
)D
,计算该选择器中伪元素及标签的数量(例如p:first-letter
这样的选择器,计算为0, 0, 0, 2
)
我们可以根据上面的规则来对应看看下面这些示例
1 | * {} /* a = 0 b = 0 c = 0 d = 0 ==> specificity = 0, 0, 0, 0 */ |
根据这样的定义,在网上有很多文章简单地把规则归纳为
针对内联样式的权重值是
1000
,Id
选择器的权重值是100
,Class
选择器的权重值是10
,标签选择器的权重值是1
,整条规则中的所有选择器权重值相加得到整个样式规则的权重值,数字越大权重值越高
大多数情况下,按照这样的理解得出的结论没有问题,但是遇到下面这样的情况就出现问题了
1 | /* 样式一 */ |
如果按照错误的计算方法来进行计算的话,样式一的权重值是 11
,样式二的权重值是 10
,所以如果这两条规则用于同一个元素,则该元素应该是红色,但是实际的结果却是蓝色的,所以我们按照规范当中提到的方式再次来看看上面的示例,针对例子中的样式一权重值应该是 0, 0, 0, 11
,而样式二的权重值是 0, 0, 1, 0
,根据规范
计算权重值时
A, B, C, D
四组值,从左到右,分组比较,如果A
相同,比较B
, 如果B
相同,比较C
,如果C
相同,比较D
,如果D
相同,则后定义的优先
我们可以得出,样式二和样式一的 A、B
相同,而样式二的 C
大于样式一,所以不管 D
的值如何,样式二权重值都大于样式一,这就是正确的答案
特殊的 !important
在按照 ABCD
四组计算比较之外,在定义样式的时候,还可以对某一个属性应用 !important
,CSS
规范中规定
!important
用于单独指定某条样式中的单个属性,对于被指定的属性,有!important
指定的权重值大于所有未用!important
指定的规则
例如下面这个示例
1 | /* 样式一 */ |
就整条规则而言,样式一的权重值为 0, 1, 1, 3
,而样式二的权重值仅为 0, 0, 0, 2
,所以应用于相同元素时,应该样式一生效,但是对于 color
这个属性,由于在样式二中用 !important
做了指定,因此 color
将应用样式二的规则,而 font-weight
则按照规定用样式一的规则
因此 !important
的作用只有在具有唯一性时才能提现,但是我们永远无法预料自己什么时候又需要覆盖一个已经指定了 !important
的属性,所以最好的办法就是不要使用 !important
这里有一个需要注意的地方,如果多条规则中都对同一个属性指定了
!important
呢?这时候!important
的作用相互抵销,依然按照ABCD
四组计算比较,更多详细内容可以参考MDN
上的 例外的 !important 规则
下面是一些来自 MDN
上的一些经验法则
- 一定要优先考虑使用样式规则的优先级来解决问题而不是
!important
- 只有在需要覆盖全站或外部
CSS
的特定页面中使用!important
(比如引入了某些框架) - 永远不要在全站范围的
CSS
上使用!important
- 永远不要在你的插件中使用
!important
取而代之,你可以
- 更好的利用
CSS
级联属性 - 使用更具体的规则,在您选择的元素之前增加一个或多个元素,使选择器变得更加具体,并获得更高的优先级
比如下面这个例子
1 | <div id="test"> |
1 | div#test span { color: green } |
上面例子中,无论你 CSS
语句的顺序是什么样的,文本都会是绿色的,因为这一条规则是最具有针对性,优先级最高的(同理,无论语句顺序怎么样,蓝色的规则都会覆盖红色的规则),我们在之前提到了 !important
的相关规则和使用方式,但是在某些情况下,使用 !important
可能是无可避免的,那么我们该如何来覆盖 !important
呢,主要有下面三种方式
- 再添加一条带
!important
的CSS
规则,其实就是要么给这个选择器更高的优先级(添加一个标签,Id
或Class
),或是添加一样的选择器,把它的位置放在原有声明的后面 - 使用相同的选择器,但是置于已有的样式之后
- 或者改写原来的规则,以避免使用
!important
关于 inherit
除了直接指定到元素上的样式规则以外,每个属性值还有一个可能为 inherit
的值,表示元素的该样式属性继承自父级元素,与父级元素的定义一致,比如
1 | <ul class="list"> |
1 | .list .item { color: red } |
在上例当中,我们将 li
的 color
样式设定为红色,但是并未针对 span
标签指定 color
属性,但是 span
中的文字会显示为红色,这就是因为 span
的 color
属性默认值为 inherit
,对于 inherit
的属性,只要记住一点,即继承而来的属性值,权重永远低于明确指定到元素的定义,只有当一个元素的某个属性没有被直接指定时,才会继承父级元素的值,我们稍微调整一下
1 | .list .item { color: red } |
同样的例子,第一条规则按照 ABCD
四组计算的权重为 0, 0, 2, 0
,第二条规则的权重为 0, 0, 0, 1
,虽然第一条规则的权重更高,但是它是针对 li
元素的直接指定,并不是针对 span
元素定义的,所以计算 span
的 color
属性权重值时,实际上就是 inherit
的红色与直接指定的蓝色的对比,按照规则,只要有直接指定的值(蓝色),就不会再取继承的值(红色),所以 span
中的文字显示为蓝色
这条规则最典型的场景就是链接文字的颜色,由于一般浏览器自带的样式表都有针对 a
标签的颜色及下划线的直接指定,所以网页样式表中对 a
标签的父级元素指定 color
属性及 text-decoration
属性,通常不会起作用
1 | <p class="txt"> |
1 | .txt { |
我们在浏览器可以看到,尽管我们给 a
标签的父级 p
设置了颜色红色和去除下划线,但是 a
标签依然是蓝色的和带下划线的,即使你给它们都加上 !important
也无效,但是我们可以通过下面的 reset
来改变这一点
1 | a { |
可以看到,父级设置的样式生效了,由于我们的样式表对 a
标签直接指定了 color
和 text-decoration
属性值,覆盖了浏览器的默认样式,所以在没有特别指定 a
标签的颜色和下划线定义的前提下,会从父级元素 p
继承,因此链接会显示红色和没有下划线
特别补充,
inherit
在CSS1
规范中并未定义,所以IE6/IE7/IE8
的QuirksMode
不支持
关于 a
标签,我们再来看下面这个有趣的例子
1 | <a href="#">鼠标划入时,我的颜色是?</a> |
1 | a:hover { |
我们希望的效果是鼠标移入 a
标签的时候,文字变成红色的,但是实际仍然是蓝色,那么为什么会这样呢?其实,a
标签的四个伪类(:link
、:hover
、active
、visited
)的优先级是一样的,所以这时候就看它们在样式文件中的顺序了,后面的会覆盖前面的,为了避免出现这样的情况,我们在写 a
标签的伪类的时候,要注意它们的顺序,遵循 :link
、:visited
、:hover
、:active
实例
最后我们再来看一个实例,加深一下印象,如下,请问两个 p
标签的文本颜色分别是什么?
1 | <p class="blue red">我的颜色是?</p> |
1 | .red { |
你可能觉得一个颜色为红,一个为蓝,但是运行后可以发现,它们都是蓝色的,所以这里就需要注意了,class
中的类名的顺序并不会影响样式的优先级,而是由它们在样式文件中的先后顺序决定的,后面定义的优先级更高
总结
- 权重决定了你的
CSS
规则怎么样被浏览器解析直到生效 - 权重决定了哪一条规则会被浏览器应用到元素上
- 每个选择器都有自己的权重
- 权重的大小跟选择器的类型和数量有关
- 样式的优先级跟样式的定义顺序有关,后面的覆盖前面的
- 一条样式规则的整体权重值包含四个独立的部分
[A, B, C, D]
A
表示内联样式,只有1
或0
两个值B
表示选择器中Id
的数量C
表示选择器中类、属性、伪类选择器的数量D
表示选择器中伪元素及标签的数量- 比较时,从低位到高位(从
A
到D
)分别比较,高位相同时才比较低位 - 如果两个选择器作用到同一元素上,则权重高者生效
- 如果两个选择器权重相同,则最后定义的规则被计算到权重中(后面定义的
CSS
规则权重要更大,会覆盖前面的CSS
规则) - 标签选择器的权重永远都比一个类选择器的权重低,无论有多少个,除非使用
!important
- 有
!important
标记指定的属性权重值最高,多次指定时相互抵消,应尽量减少!important
的使用 inherit
而来的属性定义,优先级低于任何直接指定的属性值