文章

一句话概括

伪类(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

关键差异

  • 伪类不产生新盒子,只是给已有元素打标签
  • 伪元素产生新盒子(虚拟元素),受 displayposition 等布局属性影响
  • 伪元素默认 displayinline,所以 ::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 的显示?

参考答案:

  1. content 必须设置(没有 content 伪元素不显示)
  2. 默认 display: inline,要设置宽高需改为 block/inline-block/flex
  3. 伪元素默认继承父元素的字体、颜色等属性
  4. 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 伪类和伪元素配合。

总结与扩展

核心要点

  1. 伪类 = 状态/位置选择(单冒号 :
  2. 伪元素 = 虚拟元素/片段(双冒号 ::
  3. :nth-child vs :nth-of-type 的区别是高频考点
  4. ::before/::after 必须配合 content 使用
  5. 伪元素不在 DOM 中,但可用 getComputedStyle(el, '::before') 读取

延伸学习方向

  • CSS 选择器 Level 4:has():is():where():not() 的新选择器
  • 自定义元素与伪元素:Web Components 的 ::part 和 ::slotted
  • @layer 与优先级管理:CSS 层叠上下文的新机制

相关主题

本文由作者按照 CC BY 4.0 进行授权

© 独行的风. 保留部分权利。

本站采用 Jekyll 主题 Chirpy

本站总访问量 本站访客数 本文阅读量