CSS 的 I-C-E 特指度

CSS 的样式有优先级,优先级高的会覆盖低的,例如行内样式优先级高更,会覆盖外部 CSS 文件里面的声明。 实际上,对于同一个 HTML 元素而言,如果有多条的 CSS 指定了它,浏览器也会根据 CSS 选择器的组合排出优先级。

这里的优先级便被称为 CSS 特指度。不过这个名字比较专业,很多人听不懂,大部分情况我们还是叫它 CSS 优先级


特指度表示一个 CSS 选择器的重要程度:通过公式计算出来一个值,值越大便越重要,优先级越高。
具体规则如下:

  • ID 选择器(#id)比重最大;
  • class 选择器(.class)比重中等;
  • 元素选择器(div)和伪元素选择器(::before)的比重最小;
  • 通配符选择器 (*)、关系标识符(>、空格等)对优先级没有影响。

也可以简记为:ID > class > 元素。将这三种选择的首字母连起来,形成了 “I-C-E” 这个词,这也是 “I-C-E 特指度” 这个词的由来。

如果元素的某个样式属性被多条 CSS 配置了,那么会以选择器特指度更高的 CSS 为准。具体的比较方式是:
从最大比重的选择器(ID 选择器)开始比较;如果相同,则使用较小的选择器继续比较;如果三个选择器比重均相同,则按照 CSS 规则出现的先后顺序为准,后出现的覆盖先出现的。

特指度的写法是 (数字, 数字, 数字),最左边的是比重最大的 ID 选择器的个数,中间的是 class 选择器的个数,最右边的是元素和伪元素选择器的个数。比较时,也是从左边开始比。

例如:

  • p 的特指度是 (0, 0, 1)
  • p.large 的特指度是 (0, 1, 1)
  • #content 的特指度是 (1, 0, 0)
  • div p#large ul.list li 的特指度是 (1, 1, 4)

以前还有一种计算方式是这样:ID 选择器是 100 分,class 选择器是 10 分,元素选择器是 1 分。
计算某一个 CSS 选择器的特指度,只要把它的各个选择器部分的分支相加。

例如:
div.username 可当做 “特指度 11”,因为有 1 个 class、1 个元素选择器;
main div#userinfo div.highlight.large 可当做 “特指度 123”,有 1 个 ID、2 个 class、3 个元素选择器。

注意:这里面的分值只是方便理解,千万不要认为写了 11 个 class 选择器放在一起,它的优先级就能超过 ID 选择器了,实际上是不可能的。


高特指度的选择器设置的样式会覆盖低特指度的选择器的样式;

<div class="main" id="main"></div>

<style>
#main {
background-color: blue;
height: 100px;
}

.main {
background-color: red;
width: 200px;
}
</style>

上面的 div 标签的背景色最终是蓝色,因为 ID 选择器特指度更高,浏览器遇到相同的 background-color 样式属性时,特指度更高的 ID 选择器覆盖 class 选择器的值。

另外,这个div元素的高度为 100px,宽度为 200px,因为 heightwidth 并不是同一属性,因此他们共同生效,都会作用于这个元素上。


浏览器在组合计算 CSS 样式时,还有以下规则:

  • 对于同一个元素的样式:按以下顺序生效:!important > 内联样式 > ID 选择器 > class/伪类/属性选择器 > 元素/伪元素选择器;

  • 同种类的样式,优先级高的覆盖优先级低的:例如内联样式设置了 height: 100px,而外部 CSS 文件定义的是 height: 500px,这时内联样式优先级高,元素的实际高度是 100px;

  • 不同种类的样式,是会共同生效的:例如内联样式设置了 margin-top: 100px,而外部样式设置了 margin-bottom: 100px,那么这个元素的上下外边距均是 100px,这两条样式都是生效的;

  • 在同一个地方多次声明某个样式,后声明的会覆盖先声明的:例如一个元素内联样式是 height: 10px; height: 50px;,那么它的高度就是 50px,后声明的会覆盖之前的。


我们做 Web 开发时,经常能遇到自己写的样式无法生效的场景,很多情况都是因为自己的选择器优先级不够,被其他的选择器覆盖了。例如:

/* 这样写无法生效,被其他更高优先级的选择器覆盖了 */
.user-info {
background-color: #ccc;
}

/* 这样写说不定就可以了,或者把 .user-info 再多重复一两次 */
.user-info.user-info {
background-color: #ccc;
}

这就给了我们启发:

  • 开发 UI 组件库的时候,尽量使用 class 选择器、元素选择器等低优先级的选择器,尽量避免使用 ID 选择器,这样可以方便使用者根据需求对 UI 组件库中的样式进行覆盖;
  • 可以注意到,例如 emotion 等 CSS-in-JS 的方案,运行时也是把我们写的内联样式转化为 class 选择器附加上去,而不是直接向元素写入内联样式,这样的目的也是方便开发者去覆盖;
  • 因为用户很难修改 UI 组件库内部的代码,所以用户如果想要定制 UI 组件内部元素的样式,必须使用选择器来组合;也正因此 UI 组件库通常会给元素加上很多 class,甚至会使用 BEM 规范(例如 antd);
  • 我们如果从事 UI 组件库开发,也要养成这种意识:哪怕组件库的作者并不打算给某个元素写样式,也最好给这个元素加个 class,因为用户的需求千奇百怪,用户很有可能正好需要定制这里的样式,加上了 class 可以方便用户来覆写样式