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
,因为 height
和 width
并不是同一属性,因此他们共同生效,都会作用于这个元素上。
浏览器在组合计算 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 可以方便用户来覆写样式。