CSS 伪类与伪元素
伪类和伪元素是 CSS 的语法糖
很多人以为伪类和伪元素是 CSS 的"特殊"机制,实际上它们只是选择器的延伸。伪类让我们能够选择元素的特定状态或结构位置,伪元素则允许我们创建和样式化文档中不存在的新内容。
理解伪类和伪元素的本质区别——伪类是选择已有元素的不同状态,伪元素是创建新元素——是避免混淆的关键。
面试定位:伪类和伪元素的区别是 CSS 面试的基础题。面试官还可能问到 :nth-child 的用法、::before 和 ::after 的实际应用、以及 :is() 和 :where() 的特异性差异。
伪类(Pseudo-classes)
伪类的分类体系
1. 状态伪类(User Action Pseudo-classes)
/* 链接相关状态 */
a:link { } /* 未访问的链接 */
a:visited { } /* 已访问的链接 */
a:hover { } /* 鼠标悬停 */
a:focus { } /* 获得焦点 */
a:active { } /* 激活状态(点击中) */
/* 表单元素状态 */
input:focus { } /* 获得焦点 */
input:focus-visible { } /* 键盘焦点(而不是鼠标点击) */
input:disabled { } /* 禁用状态 */
input:enabled { } /* 启用状态 */
input:checked { } /* 选中状态(radio/checkbox) */
input:indeterminate { } /* 不确定状态 */
input:valid { } /* 验证通过 */
input:invalid { } /* 验证失败 */
input:placeholder-shown { } /* 占位符显示时 */
2. 结构伪类(Structural Pseudo-classes)
/* 关系型 */
:first-child { } /* 父元素的第一个子元素 */
:last-child { } /* 父元素的最后一个子元素 */
:only-child { } /* 父元素的唯一子元素 */
:first-of-type { } /* 同类型中的第一个 */
:last-of-type { } /* 同类型中的最后一个 */
:only-of-type { } /* 同类型中的唯一 */
/* nth-child 系列 */
:nth-child(2) { } /* 第二个子元素 */
:nth-child(2n) { } /* 偶数子元素 */
:nth-child(2n+1) { } /* 奇数子元素 */
:nth-child(-n+3) { } /* 前三个 */
:nth-child(n+4):nth-child(-n+8) { } /* 4-8 */
/* 公式示例 */
:nth-child(3n+1) { } /* 1, 4, 7, 10... */
:nth-child(3n) { } /* 3, 6, 9, 12... */
:nth-child(odd) { } /* 奇数 */
:nth-child(even) { } /* 偶数 */
/* 其他 */
:nth-last-child(n) { } /* 从后往前数 */
:nth-of-type(n) { } /* 同类型从前往后 */
:nth-last-of-type(n) { } /* 同类型从后往前 */
/* 空元素 */
:empty { } /* 没有子元素的元素 */
/* 根元素 */
:root { } /* 文档根元素,等同于 html */
3. 逻辑伪类(Logical Pseudo-classes)
/* not 否定 */
:not(.active) { } /* 不包含 active 类的元素 */
/* is / where 匹配列表 */
:is(h1, h2, h3) { } /* 匹配 h1, h2, h3 */
:where(h1, h2, h3) { } /* 同样匹配 */
/* has 父元素选择器(现代浏览器支持) */
:has(.child) { } /* 包含 .child 子元素的元素 */
:has(+ .sibling) { } /* 紧接 .sibling 兄弟的元素 */
/* lang 语言 */
:lang(en) { } /* 语言为英语的元素 */
:nth-child 的深入理解
/* 常见误解:nth-child 选择的是"第 n 个子元素且满足选择器" */
/* 正确理解:nth-child(an+b) 是一个公式,选择位置满足 an+b 的元素 */
/* 例:选择前三个 */
li:nth-child(-n+3) {
background: lightblue;
}
/* 例:3 的倍数 */
li:nth-child(3n) {
background: lightgreen;
}
/* 例:选择倒数第二个 */
li:nth-child(-n+2) {
/* 这会选择第倒数第2和倒数第1个 */
}
伪元素(Pseudo-elements)
伪元素的本质
伪元素创建了文档中不存在的新元素,允许我们为其添加样式而不改变 HTML 结构。伪元素在 DOM 中不是真实元素,但可以被 CSS 选择和样式化。
常用伪元素
/* 1. ::before 和 ::after */
.element::before {
content: ""; /* 必须设置 content */
}
.element::after {
content: "→"; /* 可以是空字符串或文本 */
}
/* 2. ::first-line 和 ::first-letter */
p::first-line {
/* 第一行的样式 */
text-transform: uppercase;
}
p::first-letter {
/* 第一个字符的样式 */
font-size: 3em;
float: left;
}
/* 3. ::selection */
::selection {
background: yellow;
color: black;
}
/* 4. ::placeholder */
input::placeholder {
color: gray;
font-style: italic;
}
/* 5. ::marker(列表标记) */
li::marker {
color: red;
}
/* 6. ::cue(WebVTT 字幕) */
video::cue {
background: rgba(0, 0, 0, 0.8);
color: white;
}
::before 和 ::after 的实际应用
/* 1. 清除浮动 */
.clearfix::after {
content: "";
display: block;
clear: both;
}
/* 2. 添加图标 */
.btn-download::before {
content: "↓";
margin-right: 8px;
}
/* 3. 引用样式 */
blockquote::before {
content: """;
font-size: 3em;
color: #ddd;
float: left;
line-height: 1;
}
/* 4. 渐变遮罩 */
.clamp-text {
position: relative;
}
.clamp-text::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2em;
background: linear-gradient(transparent, white);
}
:is() vs :where() vs :has()
特异性对比
/* :is() — 取括号内最高特异性 */
:is(h1, .title) { } /* 特异性:0,1,0(class 选择器的特异性) */
:is(#header, h2) { } /* 特异性:1,0,0(ID 选择器的特异性) */
/* :where() — 始终为 0 */
:where(h1, .title) { } /* 特异性:0,0,0 */
/* :has() — 括号内选择器的特异性 */
div:has(.child) { } /* 特异性:0,1,0(class 的特异性) */
div:has(#unique) { } /* 特异性:1,0,0(ID 的特异性) */
实际应用场景
/* :where() 用于样式覆盖(低特异性) */
.library-button { background: blue; }
/* 你的覆盖样式 */
:where(.library-button) { background: red; }
/* 特异性:0,0,0,不会被 .library-button 的 0,1,0 压制 */
/* :is() 用于简化复杂选择器 */
:is(section, article, aside) :is(h1, h2, h3) {
/* 匹配 section/article/aside 内的 h1/h2/h3 */
}
/* :has() 用于父元素选择(相对较新) */
.card:has(.badge) { } /* 有 .badge 子元素的 .card */
.card:has(+ .card) { } /* 紧接另一个 .card 的 .card */
.card:has(> .featured) { } /* 有 .featured 直接子元素的 .card */
面试高频问题
Q: 伪类和伪元素的区别是什么?
回答要点:伪类是选择已有元素的特定状态或位置(如 :hover、:first-child),元素本身在 DOM 中存在。伪元素是创建新元素(如 ::before、::first-line),虽然 DOM 中不存在,但可以被 CSS 选择和样式化。语法上的区别:伪类用单冒号(:),伪元素用双冒号(::)。
Q: :first-child 和 :first-of-type 有什么区别?
回答要点::first-child 要求元素是其父元素的第一个子元素;:first-of-type 要求元素是其父元素中同类型的第一个。换句话说,:first-of-type 只看同标签的兄弟,:first-child 看所有兄弟。
Q: :is() 和 :where() 有什么区别?
回答要点:两者都接受选择器列表。:is() 的特异性是括号内选择器的最高特异性,:where() 的特异性始终为 0。:where() 用于需要低特异性以便后续覆盖的场景,:is() 用于需要保持选择器特异性的场景。