技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTCSS

搞懂 CSS 伪类和伪元素及其区别

所属馆藏
CSS
文件格式
Markdown
原始路径
CSS/23-搞懂 CSS 伪类和伪元素 及其区别
本文目录9 个章节
  1. 什么是伪类和伪元素?
  2. 伪类有哪些?
  3. 伪元素有哪些?
  4. 经典案例分析
  5. 常见伪类示意图
  6. 伪元素
  7. 伪元素和伪类的区别
  8. 使用建议
  9. 参考资料

搞懂 CSS 伪类和伪元素及其区别

Category(分类): CSS, HTML Status: 未知

什么是伪类和伪元素?

  • 伪类(Pseudo-class):通常以单冒号 : 开头,用于选择处于某种状态、满足某种条件或位于特定结构位置的元素;
  • 伪元素(Pseudo-element):现代 CSS 通常以双冒号 :: 开头,用于选择元素的某个特定部分,或生成不对应于 HTML 节点的渲染内容。

伪类和伪元素都属于 CSS 选择器能力,不会自动在 HTML 源码中增加真实的 class 或 element。

伪类示例

下面的选择器表示:当一个链接已经访问过,并且鼠标当前悬浮在它上面时,将链接文字显示为红色:

a:visited:hover {
  color: red;
}

伪类示例:visited 与 hover

无论是 :visited 还是 :hover,它们描述的都是 <a> 元素的状态。选择器最终选中的仍然是链接元素本身,只是在元素满足相应状态时样式才会生效。

需要注意:

  • 浏览器会限制 :visited 可以修改和读取的样式,主要是为了防止网页探测用户历史记录;
  • :visited 通常只适用于带有有效 href 的链接;
  • 无痕模式不会在会话结束后保存历史,但当前会话中的访问状态和具体浏览器行为可能不同,不能简单断言“无痕模式绝对没有 :visited”;
  • DevTools 的状态模拟功能主要适合模拟 :hover:focus 等状态,不能把模拟结果当成真实历史记录。

伪元素示例

下面的语法用于设置元素首字母或首个文字片段的样式:

div::first-letter {
  color: red;
}

伪元素示例:first-letter

与伪类不同,::first-letter 不是在描述 div 的状态,而是在选择 div 内容中的一个特殊部分。它不是 HTML 中独立存在的真实节点,而是浏览器根据布局规则暴露出来的伪元素。

::first-letter 通常作用于块容器、列表项、表格单元格或 inline-block 等适合形成首字母区域的盒子。普通 inline 元素通常不能直接应用这一伪元素;可以将其改为 blockinline-block,但仍应结合实际布局测试。

从上面的案例可以得出:

  • 伪类用于根据元素的状态、结构位置或其他条件选择已经存在的元素
  • 伪元素用于选择元素的某个特殊部分,或生成不对应于 HTML 节点的内容;
  • 伪类的优先级通常按 class 选择器计算;
  • 伪元素的优先级通常按类型选择器计算。

“伪类的本质是 class、伪元素的本质是 element”只能作为入门时的类比,不能理解为 DOM 中真的增加了 class 或元素。

伪类有哪些?

按照用途,可以大致分为以下几类:

  • 用户交互伪类:active:focus:focus-visible:hover 等;
  • 链接伪类:link:visited 等;
  • 结构伪类:first-child:nth-child():first-of-type 等;
  • 表单状态伪类:enabled:disabled:required:valid:invalid 等;
  • 文档和语言伪类:target:root:lang():dir() 等;
  • 逻辑伪类:not():is():where():has() 等;
  • 媒体和显示状态伪类:fullscreen:modal 等。

下面列出一些常用伪类。兼容性会随着浏览器版本变化,表格中的描述不代替针对目标浏览器的实际测试。

伪类描述当前支持概览
:active元素处于激活状态时广泛支持
:focus元素获得焦点时广泛支持
:focus-visible浏览器判断当前焦点应以可见方式提示时现代浏览器广泛支持
:hover指针悬浮在元素上时广泛支持,触摸设备行为不同
:link链接尚未访问时广泛支持
:visited链接已经访问时广泛支持,但可设置样式受隐私限制
:first-child元素是父元素的第一个子元素时广泛支持
:last-child元素是父元素的最后一个子元素时广泛支持
:nth-child()元素匹配父元素子节点序号时广泛支持
:nth-last-child()从末尾开始匹配子节点序号时广泛支持
:only-child元素是父元素唯一子元素时广泛支持
:first-of-type元素是同类型兄弟元素中的第一个时广泛支持
:last-of-type元素是同类型兄弟元素中的最后一个时广泛支持
:nth-of-type()元素匹配同类型兄弟元素序号时广泛支持
:nth-last-of-type()从末尾开始匹配同类型兄弟元素序号时广泛支持
:only-of-type元素是同类型兄弟元素中唯一一个时广泛支持
:not(selector)元素不匹配指定选择器时广泛支持
:is()匹配参数选择器列表中的任意一个现代浏览器广泛支持
:where()匹配参数选择器列表中的任意一个,优先级为 0现代浏览器广泛支持
:has()根据后代或相关元素是否匹配来选择当前元素现代浏览器广泛支持,旧浏览器需验证
:target元素的 ID 匹配 URL 片段时广泛支持
:root元素是文档树根元素时广泛支持
:lang()元素或祖先匹配指定语言时广泛支持
:dir()元素匹配指定书写方向时现代浏览器支持较好,旧浏览器需验证
:empty元素没有子节点时广泛支持,空白文本节点也会影响匹配
:valid / :invalid表单控件是否通过约束验证时广泛支持
:required / :optional表单控件是否必填时广泛支持
:in-range / :out-of-range数值表单控件是否处于范围内时广泛支持
:read-only / :read-write元素是否处于只读或可编辑状态时广泛支持,但不只适用于表单
:enabled / :disabled表单控件是否启用或禁用时广泛支持
:fullscreen元素处于全屏状态时现代浏览器广泛支持,旧版本可能需要前缀
:blankCSS Selectors Level 4 中用于匹配用户为空的表单控件尚未广泛实现,不应直接依赖

例如,:not 需要传入一个选择器参数,不能单独写成 :not

button:not([disabled]) {
  cursor: pointer;
}

:empty 判断的是子节点,而不仅仅是“没有元素子节点”。下面的空白字符也属于文本节点:

<div class="empty"> </div>

伪元素有哪些?

与伪类相比,常见的标准伪元素数量较少:

伪元素作用当前支持概览
::first-letter选择块容器等区域中的首字母或首个文字片段广泛支持
::first-line选择元素排版后的第一行广泛支持
::before在元素内容前生成一个伪元素广泛支持
::after在元素内容后生成一个伪元素广泛支持
::placeholder设置表单控件的占位文本样式广泛支持,但可设置属性有限
::file-selector-button设置 input[type="file"] 的文件选择按钮现代浏览器支持较好
::selection设置用户选中的文本区域样式广泛支持,可设置属性有限
::backdrop设置全屏、对话框或 Popover 等顶层元素后面的背景层现代浏览器支持较好
::marker设置列表项的项目符号或编号标记现代浏览器支持较好
::cue设置视频 WebVTT 字幕提示的样式现代浏览器支持情况不完全一致
::part()设置 Shadow DOM 暴露出来的 part现代浏览器支持较好
::slotted()设置 Shadow DOM 插槽中的分发节点需要 Web Components 场景

::before::after 通常需要设置非 nonecontent 值,否则不会生成可见的伪元素:

.notice::before {
  content: '提示:';
  color: #c2410c;
}

伪元素适合装饰性内容。对用户有实际意义的文字、错误信息和操作提示不应只放在伪元素中,否则可能无法被辅助技术正确读取。

经典案例分析

添加前缀或后缀

伪元素最常见的用途之一,是利用 ::before::after 为元素增加装饰性前缀或后缀。

<div class="error-message">
  系统异常,请稍后再试
</div>
.error-message {
  position: relative;
  padding: 12px 30px;
  color: #666;
  background-color: #ffece4;
  border-radius: 5px;
}

.error-message::before {
  position: absolute;
  top: 50%;
  left: 10px;
  width: 15px;
  height: 15px;
  color: #c2410c;
  font-weight: 700;
  line-height: 15px;
  text-align: center;
  content: '!';
  transform: translateY(-50%);
}

如果项目使用 public/icon-error.svg,在 Vite 或 Nuxt 中通常应通过 /icon-error.svg 访问,而不是 /public/icon-error.svg

使用伪元素添加错误提示前缀

注意:

  • ::before::after 需要合适的 content 才能生成;
  • 绝对定位的伪元素通常需要宿主元素建立定位包含块,例如设置 position: relative
  • 纯装饰图标可以使用伪元素,但关键错误信息不能只依赖伪元素传递;
  • 如果使用图标字体或背景图片,仍需考虑高对比度和加载失败时的表现。

消息气泡

聊天界面经常会使用带箭头的消息气泡。可以通过 ::after 生成零尺寸边框三角形:

<div class="container">
  <div class="box top-arrow">Top</div>
  <div class="box right-arrow">Right</div>
  <div class="box bottom-arrow">Bottom</div>
  <div class="box left-arrow">Left</div>
</div>
.container {
  display: grid;
  grid-template-columns: 200px 200px;
  grid-template-rows: 200px 200px;
  gap: 20px;
}

.box {
  position: relative;
  display: flex;
  width: 150px;
  height: 100px;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 16px;
  background-color: red;
  border-radius: 20px;
}

.box::after {
  position: absolute;
  width: 0;
  height: 0;
  content: '';
}

/* 箭头位于盒子上方,并指向上方 */
.box.top-arrow::after {
  bottom: 100%;
  left: 22px;
  border-right: 8px solid transparent;
  border-bottom: 12px solid red;
  border-left: 8px solid transparent;
}

/* 箭头位于盒子右侧,并指向右方 */
.box.right-arrow::after {
  top: 22px;
  left: 100%;
  border-top: 8px solid transparent;
  border-bottom: 8px solid transparent;
  border-left: 12px solid red;
}

/* 箭头位于盒子下方,并指向下方 */
.box.bottom-arrow::after {
  top: 100%;
  left: 22px;
  border-top: 12px solid red;
  border-right: 8px solid transparent;
  border-left: 8px solid transparent;
}

/* 箭头位于盒子左侧,并指向左方 */
.box.left-arrow::after {
  top: 22px;
  right: 100%;
  border-top: 8px solid transparent;
  border-right: 12px solid red;
  border-bottom: 8px solid transparent;
}

使用伪元素绘制消息气泡箭头

原文示例中的箭头位置和 top-arrowbottom-arrow 等 class 名称存在对应关系混乱,已按照“箭头所在边”和“箭头指向”重新整理。

斑马条纹效果

在列表中,可以使用 :nth-child(even) 为偶数行设置不同颜色:

<div class="stripe-list">
  <div>第 1 行</div>
  <div>第 2 行</div>
  <div>第 3 行</div>
  <div>第 4 行</div>
  <div>第 5 行</div>
  <div>第 6 行</div>
</div>
.stripe-list {
  width: 200px;
  margin: 50px auto;
  border: 1px solid antiquewhite;
}

.stripe-list > div {
  padding: 5px;
}

.stripe-list > div:nth-child(even) {
  background-color: antiquewhite;
}

使用 nth-child 实现斑马条纹

:nth-child(even) 按所有同级子节点计数。如果只想按照同类型元素计数,可以使用 :nth-of-type(even)

悬浮高亮效果

在商品列表中,当用户将指针悬浮到商品卡片上时,可以通过阴影和缩放突出当前卡片:

<div class="product-list">
  <a class="product" href="/product/1" aria-label="商品 1"></a>
  <a class="product" href="/product/2" aria-label="商品 2"></a>
  <a class="product" href="/product/3" aria-label="商品 3"></a>
</div>
body {
  margin: 0;
  background-color: rgb(245 245 245);
}

.product-list {
  display: flex;
  width: 360px;
  height: 200px;
  margin: auto;
  align-items: center;
  justify-content: space-around;
}

.product {
  width: 100px;
  height: 100px;
  background-color: white;
  background-image: url('./images/pseudo-product.webp');
  background-position: center;
  background-size: cover;
  transition: transform 180ms ease, box-shadow 180ms ease;
}

.product:hover {
  z-index: 1;
  box-shadow: 0 15px 30px rgb(0 0 0 / 10%);
  transform: translateY(-5px) scale(1.2);
}

.product:focus-visible {
  outline: 3px solid #2563eb;
  outline-offset: 3px;
}

使用 hover 伪类实现商品卡片高亮

如果元素可以点击,建议使用 <a><button>,不要只给普通 div 添加 cursor: pointer。同时还应提供键盘焦点样式,不能只依赖鼠标悬浮。

自定义复选框样式

可以使用表单状态伪类 :checked 和伪元素 ::before 自定义复选框。示例需要保证每个 id 唯一,并且 labelfor 与之对应:

<div class="checkbox-list">
  <div>
    <input class="checkbox-input" type="checkbox" id="backend-1">
    <label class="checkbox-label" for="backend-1">后端开发</label>
  </div>

  <div>
    <input class="checkbox-input" type="checkbox" id="backend-2">
    <label class="checkbox-label" for="backend-2">后端开发</label>
  </div>

  <div>
    <input class="checkbox-input" type="checkbox" id="frontend-1">
    <label class="checkbox-label" for="frontend-1">前端开发</label>
  </div>

  <div>
    <input class="checkbox-input" type="checkbox" id="frontend-2">
    <label class="checkbox-label" for="frontend-2">前端开发</label>
  </div>
</div>
.checkbox-input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

.checkbox-label {
  display: inline-flex;
  gap: 0.5em;
  align-items: center;
  cursor: pointer;
}

.checkbox-label::before {
  display: inline-grid;
  width: 0.9em;
  height: 0.9em;
  place-content: center;
  color: white;
  font-size: 0.8em;
  background-color: silver;
  border-radius: 0.2em;
  content: '';
}

.checkbox-input:checked + .checkbox-label::before {
  background-color: yellowgreen;
  content: '✓';
}

.checkbox-input:focus-visible + .checkbox-label::before {
  outline: 2px solid #2563eb;
  outline-offset: 2px;
}

使用伪类和伪元素自定义复选框

不建议简单使用 opacity: 0 并禁用 label 的点击事件,因为这样可能造成焦点不可见、标签无法点击和辅助技术体验不佳。对于不需要完全自定义外观的场景,也可以直接使用浏览器原生控件的 accent-color

自定义滚动条样式

部分浏览器允许使用非标准的 ::-webkit-scrollbar 系列伪元素自定义滚动条。该方案主要适用于 Chromium 和部分 WebKit 浏览器,不是跨浏览器统一标准。

<div class="scroll-demo">
  <div class="scroll-box">
    <div class="scroll-content"></div>
  </div>

  <div class="scroll-box custom-scrollbar">
    <div class="scroll-content"></div>
  </div>
</div>
.scroll-demo {
  display: flex;
  justify-content: space-around;
  gap: 1rem;
}

.scroll-box {
  width: 200px;
  height: 200px;
  overflow: auto;
  border: 1px solid gainsboro;
}

.scroll-content {
  width: 100px;
  height: 300px;
  background-color: aliceblue;
}

.custom-scrollbar {
  scrollbar-color: rgba(0 0 0 / 20%) transparent;
  scrollbar-width: thin;
}

.custom-scrollbar::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

.custom-scrollbar::-webkit-scrollbar-track {
  background: transparent;
}

.custom-scrollbar::-webkit-scrollbar-thumb {
  background-color: rgba(0 0 0 / 20%);
  border: 3px solid transparent;
  border-radius: 999px;
  background-clip: padding-box;
}

.custom-scrollbar::-webkit-scrollbar-thumb:hover {
  background-color: rgba(0 0 0 / 35%);
}

使用伪元素和伪类自定义滚动条

原文称滚动条默认宽度为 6px、悬浮后变成 10px,但示例实际上始终设置为 10px,并没有实现宽度变化。滚动条尺寸和样式还可能被操作系统、浏览器设置和强制颜色模式覆盖,不应设置得过窄或只依赖自定义滚动条传达信息。

常见伪类示意图

常见伪类示意图

伪元素

伪元素是附加在选择器末尾的关键词,允许对被选择元素的特定部分设置样式,或者生成不在文档树中的渲染内容。

按照 CSS3 之后的规范,伪元素推荐使用双冒号,例如 ::before::after,以便与伪类区分。为了兼容旧代码,::before::after::first-letter::first-line 等早期伪元素在许多浏览器中也支持单冒号写法。

CSS 新增的伪元素通常不应使用单冒号替代双冒号。项目代码统一使用双冒号更清晰:

.card::before {
  content: '';
}

常见伪元素包括:

  • ::before / :before:在元素内容前生成伪元素;
  • ::after / :after:在元素内容后生成伪元素;
  • ::first-letter / :first-letter:设置首字母或首个文字片段;
  • ::first-line / :first-line:设置排版后的第一行;
  • ::marker:设置 display: list-item 元素的项目符号或编号;
  • ::selection:设置用户选中文本的样式;
  • ::placeholder:设置输入框占位文本的样式;
  • ::file-selector-button:设置文件输入控件的按钮;
  • ::backdrop:设置顶层元素后面的背景层。

伪元素和伪类的区别

语法区别

CSS2 时代部分伪元素和伪类都使用单冒号。CSS3 为了清晰区分两者,推荐伪类使用单冒号,伪元素使用双冒号:

button:hover {
  background: #2563eb;
}

button::before {
  content: '';
}

早期的 ::before::after::first-letter::first-line 仍然通常兼容单冒号写法,但新代码应优先使用双冒号。

含义区别

  • 伪类:描述元素的状态、结构位置或满足的条件,例如 :hover:checked:nth-child()
  • 伪元素:选择元素的特定渲染部分,或生成一个不对应真实 DOM 节点的渲染对象,例如 ::first-letter::before
  • 伪类通常可以继续与其他选择器组合,用来筛选真实元素;
  • 伪元素通常位于选择器末尾,表示真实元素内部的特定渲染区域;
  • 伪类和伪元素都不能替代真正的语义 HTML。

选择器优先级区别

伪类的优先级通常与 class 选择器相同:

button:hover {
  /* 伪类:class 选择器级别 */
}

伪元素的优先级通常与类型选择器相同:

button::before {
  /* 伪元素:类型选择器级别 */
}

实际项目中还需要结合 :where():is()、层叠层(@layer)和选择器顺序一起判断最终样式。

使用建议

  1. 使用 :hover 时,同时考虑键盘用户和触摸设备;
  2. 为可交互元素提供 :focus-visible 样式,不要只设计鼠标悬浮状态;
  3. ::before::after 适合装饰、图标和简单提示,不要承载关键业务文本;
  4. 表单校验应使用真实的 HTML 属性,例如 requireddisabledaria-invalid,不能只用颜色或伪元素提示;
  5. 自定义控件必须保留键盘操作、焦点样式和屏幕阅读器语义;
  6. 浏览器兼容性表需要注明目标浏览器和统计时间,不能使用永久不变的“支持/不支持”结论。

参考资料

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS