在学习 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而来的属性定义,优先级低于任何直接指定的属性值