一句话概括
伪类(Pseudo-classes)和伪元素(Pseudo-elements)是 CSS 选择器的两大扩展机制:伪类用单冒号 : 描述元素的状态或位置(如 :hover、:nth-child),伪元素用双冒号 :: 创建元素的虚拟子节点或片段(如 ::before、::first-line)。两者都不需要修改 HTML 结构,却能实现复杂的样式控制。
背景与意义
CSS 的”无侵入”增强
在早期 CSS 中,样式只能直接作用于 HTML 元素。但现实中有大量”基于状态或结构”的需求:
1
2
3
4
5
- 列表的第 3 项要不同样式
- 鼠标悬停按钮时变色
- 表格的奇偶行交替背景色
- 元素内容的"第一行"用大字号
- 在元素前后插入装饰内容(图标、引号)
如果每个都改 HTML,要么侵入后端模板,要么在 JS 中硬编码——都破坏了”关注点分离”的原则。
伪类和伪元素 让 CSS 自身就能处理这些场景:HTML 保持不变,CSS 通过”虚拟选择器”精确命中目标。
面试高频信号
伪类/伪元素是 CSS 必考题:
:nth-child()与:nth-of-type()的区别(高频陷阱题)::before和::after有哪些使用场景- 伪元素与 content 属性的配合
概念与定义
伪类(Pseudo-classes)
定义:选择元素的特定状态或位置,不改变元素本身。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 状态类伪类 */
a:link { color: blue; } /* 未访问 */
a:visited { color: purple; } /* 已访问 */
a:hover { color: red; } /* 鼠标悬停 */
a:active { color: orange; } /* 激活(点击瞬间) */
input:focus { outline: 2px solid blue; } /* 聚焦 */
input:disabled { opacity: 0.5; } /* 禁用 */
input:checked + label { font-weight: bold; } /* 选中 */
/* 结构类伪类 */
li:first-child { font-weight: bold; } /* 第一个子元素 */
li:last-child { border: none; } /* 最后一个子元素 */
li:nth-child(2n) { background: #f5f5f5; } /* 偶数行 */
p:empty { display: none; } /* 空元素 */
伪元素(Pseudo-elements)
定义:创建不在 DOM 中的虚拟元素,用于定位元素的特定部分或在元素前后插入内容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 常用伪元素 */
.element::before {
content: "→ ";
color: #42b883;
}
.element::after {
content: "";
display: block;
clear: both;
}
p::first-line { font-size: 1.2em; font-weight: bold; }
p::first-letter { font-size: 3em; float: left; margin-right: 4px; }
/* 选中文本样式 */
::selection { background: #42b883; color: white; }
/* 占位符样式 */
input::placeholder { color: #999; font-style: italic; }
最小示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<style>
/* 伪类:交互状态 */
.btn { padding: 12px 24px; border: 2px solid #42b883; background: white;
transition: all 0.2s; cursor: pointer; }
.btn:hover { background: #42b883; color: white; }
.btn:active { transform: scale(0.95); }
/* 伪元素:自定义列表项符号 */
.custom-list li { list-style: none; padding-left: 8px; }
.custom-list li::before { content: "✓"; color: #42b883; margin-right: 8px; font-weight: bold; }
/* 选中文本样式 */
::selection { background: #42b883; color: white; }
</style>
<button class="btn">Hover Me</button>
<ul class="custom-list">
<li>列表项一</li>
<li>列表项二</li>
<li>列表项三</li>
</ul>
<p>选中这段文字看看效果...</p>
核心知识点拆解
知识点 1::nth-child 与 :nth-of-type 的区别
🔥 面试最高频陷阱题:
1
2
3
4
5
6
7
<div class="container">
<p>段落 1</p>
<span>内联 1</span>
<p>段落 2</p>
<span>内联 2</span>
<p>段落 3</p>
</div>
1
2
3
4
5
6
7
8
9
10
11
/* 1. nth-child(2) → 选择第二个子元素,不管什么类型 */
.container :nth-child(2) { color: red; }
/* 结果:内联 1 变红(它是第二个子元素) */
/* 2. nth-of-type(2) → 选择同类型的第二个元素 */
.container p:nth-of-type(2) { color: blue; }
/* 结果:段落 2 变蓝(p 类型的第二个) */
/* 3. 关键区别!!! */
:nth-child(n) { } /* 在所有子元素中排第 n 个 */
:nth-of-type(n) { } /* 在同类型子元素中排第 n 个 */
记忆口诀:nth-child 数所有人,nth-of-type 数同类兄弟。
知识点 2:::before 和 ::after 的 content 属性
content 是伪元素的灵魂——没有 content,伪元素不显示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* content 的多种取值 */
.link::after { content: " ↗"; } /* 字符串 */
.link::before { content: url('icon.svg'); } /* 图片/图标 */
.element::before { content: attr(data-tooltip); } /* HTML 属性值 */
.element::after { content: ""; } /* 空字符串(常用) */
/* 计数器 */
.list { counter-reset: section; }
.list-item::before {
counter-increment: section;
content: "第 " counter(section) " 章: ";
}
/* 配合 attr() 显示属性 */
a::after { content: " (" attr(href) ")"; }
/* 显示为:点击这里 (https://example.com) */
常见不能用 content 的场景:不能放 HTML 标签字符串,只能放纯文本或 URL。要插入复杂内容需用伪元素 + 背景图/数据 URI。
知识点 3:伪类的”爱恨法则”(LVHA)
链接伪类的书写顺序:
1
2
3
4
5
6
7
8
9
/* ✅ 正确的顺序:LoVe HA */
a:link { } /* 未访问 */
a:visited { } /* 已访问 */
a:hover { } /* 悬停 */
a:active { } /* 激活 */
/* ❌ 错误的顺序会覆盖前面的规则 */
a:hover { }
a:link { } /* 这会覆盖 hover,因为 link 和 visited 在最后 */
知识点 4:表单伪类
1
2
3
4
5
6
7
8
9
10
11
12
13
/* 常用表单伪类组合 */
input:required { border-left: 3px solid #e74c3c; } /* 必填项 */
input:valid { border-color: #42b883; } /* 验证通过 */
input:invalid { border-color: #e74c3c; } /* 验证失败 */
input:focus:invalid { box-shadow: 0 0 4px #e74c3c; } /* 聚焦+无效时高亮 */
input:placeholder-shown { background: #fff9c4; } /* 占位符可见时 */
/* ⚠️ :valid/:invalid 的问题:初始状态就会生效 */
/* 解决方案:结合 :placeholder-shown 或添加 was-focused 类 */
input:not(:placeholder-shown):invalid {
border-color: #e74c3c;
background: #ffe0e0;
}
实战案例
案例一:纯 CSS 工具提示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.tooltip-wrapper { position: relative; cursor: pointer; }
.tooltip-wrapper::after {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
padding: 6px 12px;
background: #333;
color: white;
font-size: 12px;
border-radius: 4px;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
}
.tooltip-wrapper:hover::after {
opacity: 1;
}
1
<button class="tooltip-wrapper" data-tooltip="保存成功!">鼠标悬停查看提示</button>
案例二:清除浮动
1
2
3
4
5
6
7
8
9
10
11
12
/* ::after 清除浮动(经典做法) */
.clearfix::after {
content: "";
display: table;
clear: both;
}
/* 使用 */
<div class="clearfix">
<div style="float: left;">左浮动</div>
<div style="float: right;">右浮动</div>
</div>
案例三:奇偶行斑马纹
1
2
3
4
5
table tr:nth-child(even) { background: #f8f9fa; }
/* 复合表达式 */
li:nth-child(3n+1) { color: #e74c3c; } /* 第 1、4、7…项 */
li:nth-child(-n+3) { font-weight: bold; } /* 前 3 项 */
li:nth-child(2n+1) { background: #f0f0f0; } /* 奇数项(等价于 odd) */
案例四:自定义复选框
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* 隐藏原生复选框 */
.checkbox-input { position: absolute; opacity: 0; }
/* 自定义外观 */
.checkbox-label::before {
content: "";
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid #ccc;
border-radius: 4px;
margin-right: 8px;
vertical-align: middle;
transition: all 0.2s;
}
/* 选中状态 */
.checkbox-input:checked + .checkbox-label::before {
background: #42b883;
border-color: #42b883;
background-image: url("data:image/svg+xml,..."); /* 勾号图标 */
}
底层原理
伪元素与浏览器的渲染机制
伪元素在 CSSOM(CSS Object Model)中被处理为独立的虚构元素:
1
2
3
4
5
6
7
8
9
10
HTML 解析 → DOM 树
↓
CSSOM 构建
↓
样式计算阶段 —— 伪类通过状态匹配元素
伪元素创建虚拟节点并继承父元素可继承属性
↓
布局阶段 —— 伪元素参与布局计算(display 属性决定是否创建盒子)
↓
绘制阶段 —— 标准元素绘完后绘制 ::after / ::before
关键差异:
- 伪类不产生新盒子,只是给已有元素打标签
- 伪元素产生新盒子(虚拟元素),受
display、position等布局属性影响 - 伪元素默认
display为inline,所以::before::after设置宽高前要先改display: block/inline-block
伪元素与 DOM 的关系
伪元素不在 DOM 树中,但可以在计算样式中查到:
1
2
3
4
5
6
7
8
9
10
11
// ✅ 可以拿到伪元素的计算样式
const style = getComputedStyle(element, '::before');
console.log(style.content); // "✓"
console.log(style.color); // "rgb(66, 184, 131)"
// ❌ 伪元素不能被 JS 直接选中
document.querySelector('::before'); // null
element.querySelector('::before'); // null
// 但是可以修改伪元素的样式(通过 CSS 变量)
element.style.setProperty('--icon-color', 'red');
高频面试题解析
Q1:伪类和伪元素的区别?
参考答案:
- 伪类(
:):描述元素的状态或位置,如:hover、:nth-child(2)。不产生新元素。 - 伪元素(
::):创建不在 DOM 中的虚拟元素,如::before、::first-line。产生新盒子。
CSS3 规范建议:伪类用单冒号 :,伪元素用双冒号 ::(但浏览器为了向后兼容也支持单冒号格式)。
Q2::nth-child 和 :nth-of-type 的区别?
参考答案:
:nth-child(n):在所有同层子元素中排第 n 个,不管元素类型:nth-of-type(n):在同类型兄弟元素中排第 n 个
例如容器内有 <p> 和 <span> 交替出现时,:nth-child(2) 选中 <span>,p:nth-of-type(2) 选中第二个 <p>。
Q3:哪些属性会影响 ::before / ::after 的显示?
参考答案:
content必须设置(没有 content 伪元素不显示)- 默认
display: inline,要设置宽高需改为block/inline-block/flex - 伪元素默认继承父元素的字体、颜色等属性
position: absolute常用于定位伪元素
Q4:纯 CSS 如何实现 tooltip?
参考答案: 使用 ::after + attr() + hover:
1
2
3
4
5
6
7
.tooltip::after {
content: attr(data-tip);
position: absolute;
opacity: 0;
/* 样式... */
}
.tooltip:hover::after { opacity: 1; }
不需要任何 JS,全靠 CSS 伪类和伪元素配合。
总结与扩展
核心要点
- 伪类 = 状态/位置选择(单冒号
:) - 伪元素 = 虚拟元素/片段(双冒号
::) :nth-childvs:nth-of-type的区别是高频考点::before/::after必须配合content使用- 伪元素不在 DOM 中,但可用
getComputedStyle(el, '::before')读取
延伸学习方向
- CSS 选择器 Level 4:
:has()、:is()、:where()、:not()的新选择器 - 自定义元素与伪元素:Web Components 的 ::part 和 ::slotted
- @layer 与优先级管理:CSS 层叠上下文的新机制