CSS 选择器优先级与组合
CSS 选择器的优先级计算、组合选择器、伪类与伪元素的使用边界。
#frontend
#css
#concept
#growing
[!info] related notes
- 所属 MOC: CSS MOC
- 并列概念: CSS 选择器、伪类与伪元素
- 相关概念: css-variable, CSS 优先级与特异性
CSS 选择器优先级与组合
[!note] 边界 特异性(specificity)的 canonical 规则与层叠比较见 CSS 优先级与特异性。本文保留选择器组合、匹配方式以及“组合后如何影响特异性”的速查,不再把
CSS Specificity作为自身身份别名。
定义
选择器组合决定匹配哪些元素;特异性是匹配之后、层叠解决声明冲突时的一个比较维度。来源、important 和层叠层等条件先于特异性比较。
优先级计算
更准确地说,selector specificity 通常按三列比较:ID、CLASS、TYPE。内联 style 属于声明来源语境;!important 会改变层叠比较层级,二者都不应当被塞进 selector specificity 本身。
- ID:ID 选择器数量
- CLASS:类、属性选择器、伪类数量
- TYPE:元素选择器、伪元素数量
进入特异性比较后,先比较 ID,再比较 CLASS,最后比较 TYPE;三列相同时,再按作用域接近程度和源顺序等后续规则处理。
/* specificity: (0, 0, 1) */
p { color: black; }
/* specificity: (0, 1, 0) — 更高 */
.text { color: blue; }
/* specificity: (1, 0, 0) — 更高 */
#title { color: red; }
/* specificity: (0, 1, 1) */
p.intro { color: green; }
/* specificity: (0, 1, 2) */
div p.intro { color: purple; }
常见选择器的优先级
| 选择器 | 优先级 |
|---|---|
*(通配符) | (0, 0, 0) |
p(元素) | (0, 0, 1) |
.class(类) | (0, 1, 0) |
:hover(伪类) | (0, 1, 0) |
[type="text"](属性) | (0, 1, 0) |
#id | (1, 0, 0) |
特殊情况
- 通配符
*不影响优先级 :is()、:not()、:has()取参数列表中最高的特异性;:where()连同参数始终为 0- 只有前面的层叠条件和作用域接近程度等也相同时,源顺序才决定胜负
组合选择器
/* 后代选择器:匹配 .container 内所有 p */
.container p { }
/* 子元素选择器:只匹配直接子元素 */
.nav > li { }
/* 相邻兄弟选择器:紧跟其后的兄弟 */
h2 + p { }
/* 通用兄弟选择器:之后的所有兄弟 */
h2 ~ p { }
组合选择器对优先级的影响
/* 后代选择器:两个元素选择器 */
div p { } /* (0, 0, 2) */
/* 交集选择器:类 + 元素 */
p.active { } /* (0, 1, 1) */
/* 多类选择器 */
.btn.primary { } /* (0, 2, 0) */
常用伪类
/* 状态伪类 */
a:hover { }
input:focus { }
button:active { }
a:visited { }
/* 结构伪类 */
li:first-child { }
li:last-child { }
li:nth-child(2n) { } /* 偶数行 */
li:nth-child(3n+1) { } /* 每三个一组的第一个 */
li:not(.disabled) { }
/* 表单伪类 */
input:valid { }
input:invalid { }
input:required { }
input:disabled { }
input:checked { }
常用伪元素
/* 虚拟内容 */
.icon::before { content: "★"; }
.tooltip::after { content: attr(data-tip); }
/* 文本样式 */
p::first-line { font-weight: bold; }
p::first-letter { font-size: 2em; }
/* 选中样式 */
::selection { background: yellow; }
/* 占位符 */
input::placeholder { color: #999; }
实际开发中的经验
避免过度嵌套
/* 不推荐:高优先级,难覆盖 */
body .main .content .card .title .text { }
/* 推荐:低优先级,易维护 */
.card-text { }
不要滥用 !important
/* 不推荐 */
.btn { background: red !important; }
/* 推荐:用更具体的选择器 */
.btn.btn-primary { background: red; }
伪类 vs 伪元素
- 伪类用一个冒号
:— 描述元素的状态 - 伪元素用两个冒号
::— 描述元素的虚拟部分 - 旧式
:before/:after仍被兼容;新代码使用双冒号,以区分伪元素与伪类
边界
- 内联声明与选择器声明的比较属于完整 cascade;不要把它当作 selector specificity 的“第四列”
- cascade layer 的顺序在 specificity 之前比较,因此不同 layer 之间不能只靠 specificity 判断胜负
- 优先级是选择器级别的,不是属性级别的
- 来源、important、层、特异性与作用域接近程度等都相同时,后出现的声明胜出
- 伪类和属性选择器的优先级相同
一句话记忆法
先确认匹配,再按完整层叠规则比较;特异性按 ID、类、类型逐列比较。不要把加深选择器当作通用修复手段。