伪类和伪元素让你可以选择特定状态的元素或元素的特定部分。它们是 CSS 选择器的重要扩展,能实现很多常规选择器无法做到的效果。
伪类 vs 伪元素#
- 伪类(
:pseudo-class):选择处于特定状态的元素 - 伪元素(
::pseudo-element):选择元素的特定部分
🎯 区分方法:伪类用单冒号 :,伪元素用双冒号 ::(为了向后兼容,老的伪元素如 ::before 也接受单冒号)。
常用伪类#
交互状态伪类#
/* 鼠标悬停 */a:hover { color: #3b82f6;}
/* 获得焦点 */input:focus { border-color: #3b82f6; outline: none;}
/* 激活(按下瞬间) */button:active { transform: scale(0.98);}
/* 已访问的链接 */a:visited { color: #6b7280;}表单状态伪类#
/* 禁用状态 */input:disabled { background-color: #f3f4f6; cursor: not-allowed;}
/* 启用状态 */input:enabled { background-color: white;}
/* 选中的复选框/单选 */input:checked { accent-color: #3b82f6;}
/* 必填字段 */input:required { border-left: 3px solid #ef4444;}
/* 可选字段 */input:optional { border-left: 3px solid #10b981;}
/* 验证通过 */input:valid { border-color: #10b981;}
/* 验证失败 */input:invalid { border-color: #ef4444;}
/* 只读 */input:read-only { background-color: #f9fafb;}
/* 显示占位符时 */input:placeholder-shown { font-style: italic;}结构伪类#
/* 第一个子元素 */li:first-child { font-weight: bold;}
/* 最后一个子元素 */li:last-child { border-bottom: none;}
/* 唯一子元素 */p:only-child { margin: 0;}
/* 第 n 个子元素 */li:nth-child(3) { color: red;}
/* 奇数位置 */tr:nth-child(odd) { background-color: #f9fafb;}
/* 偶数位置 */tr:nth-child(even) { background-color: white;}
/* 每第 3 个 */li:nth-child(3n) { color: blue;}
/* 从第 3 个开始每隔 2 个 */li:nth-child(2n + 3) { font-style: italic;}
/* 前 3 个 */li:nth-child(-n + 3) { font-weight: bold;}
/* 倒数第 n 个 */li:nth-last-child(2) { color: gray;}类型伪类#
/* 同类型的第一个 */p:first-of-type { font-size: 1.2em;}
/* 同类型的最后一个 */p:last-of-type { margin-bottom: 0;}
/* 同类型的第 n 个 */img:nth-of-type(2) { float: right;}
/* 唯一该类型的元素 */img:only-of-type { display: block; margin: 0 auto;}其他伪类#
/* 空元素 */div:empty { display: none;}
/* 根元素 */:root { --primary-color: #3b82f6;}
/* 目标元素(URL 锚点匹配) */section:target { background-color: #fef3c7;}
/* 焦点在子元素内 */.form-group:focus-within { border-color: #3b82f6;}
/* 仅键盘焦点 */button:focus-visible { outline: 2px solid #3b82f6;}伪元素#
::before 和 ::after#
在元素内容前/后插入内容:
/* 必须有 content 属性 */.quote::before { content: '' ';}
.quote::after { content: ' '';}
/* 装饰性图标 */.external-link::after { content: ' ↗'; font-size: 0.75em;}
/* 清除浮动 */.clearfix::after { content: ''; display: block; clear: both;}content 属性值#
/* 文本 */.note::before { content: '注意:';}
/* 属性值 */a::after { content: ' (' attr(href) ')';}
/* 计数器 */li::before { content: counter(item) '. '; counter-increment: item;}
/* 图片 */.icon::before { content: url('icon.svg');}
/* 空字符串(用于纯装饰) */.decorated::before { content: ''; display: block; width: 50px; height: 2px; background: #3b82f6;}::first-line 和 ::first-letter#
/* 首行 */p::first-line { font-weight: bold; color: #1f2937;}
/* 首字母(大写字母效果) */.article p:first-of-type::first-letter { font-size: 3em; float: left; line-height: 1; margin-right: 0.1em; color: #3b82f6;}::selection#
选中文本的样式:
::selection { background-color: #3b82f6; color: white;}
/* 特定元素的选中样式 */.code::selection { background-color: #fef3c7; color: #1f2937;}::placeholder#
输入框占位符样式:
input::placeholder { color: #9ca3af; font-style: italic;}::marker#
列表标记样式:
li::marker { color: #3b82f6; font-weight: bold;}
ol li::marker { content: counters(list-item, '.') ' ';}实战案例#
自定义复选框#
.checkbox { position: relative; display: inline-flex; align-items: center; cursor: pointer;}
.checkbox input { position: absolute; opacity: 0; width: 0; height: 0;}
.checkbox .checkmark { width: 20px; height: 20px; border: 2px solid #d1d5db; border-radius: 4px; margin-right: 0.5rem; transition: all 0.2s;}
/* 选中状态 */.checkbox input:checked + .checkmark { background-color: #3b82f6; border-color: #3b82f6;}
/* 选中后的勾 */.checkbox input:checked + .checkmark::after { content: ''; position: absolute; left: 7px; top: 3px; width: 5px; height: 10px; border: solid white; border-width: 0 2px 2px 0; transform: rotate(45deg);}
/* 焦点状态 */.checkbox input:focus-visible + .checkmark { box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);}
/* 禁用状态 */.checkbox input:disabled + .checkmark { background-color: #f3f4f6; cursor: not-allowed;}<label class="checkbox"> <input type="checkbox" /> <span class="checkmark"></span> 记住我</label>工具提示#
.tooltip { position: relative; cursor: help;}
.tooltip::after { content: attr(data-tip); position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); padding: 0.5rem 0.75rem; margin-bottom: 8px; background-color: #1f2937; color: white; font-size: 0.875rem; white-space: nowrap; border-radius: 6px; opacity: 0; visibility: hidden; transition: all 0.2s; z-index: 100;}
/* 小三角 */.tooltip::before { content: ''; position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); border: 6px solid transparent; border-top-color: #1f2937; opacity: 0; visibility: hidden; transition: all 0.2s;}
.tooltip:hover::after,.tooltip:hover::before { opacity: 1; visibility: visible;}<span class="tooltip" data-tip="这是提示内容">悬停查看</span>表单验证反馈#
.form-group { position: relative; margin-bottom: 1.5rem;}
.form-input { width: 100%; padding: 0.75rem 1rem; padding-right: 2.5rem; border: 1px solid #d1d5db; border-radius: 6px; transition: border-color 0.2s;}
.form-input:focus { outline: none; border-color: #3b82f6;}
/* 验证图标位置 */.form-group::after { position: absolute; right: 0.75rem; top: 50%; transform: translateY(-50%); font-size: 1.25rem;}
/* 验证通过 */.form-input:valid:not(:placeholder-shown) { border-color: #10b981;}
.form-group:has(.form-input:valid:not(:placeholder-shown))::after { content: '✓'; color: #10b981;}
/* 验证失败 */.form-input:invalid:not(:placeholder-shown) { border-color: #ef4444;}
.form-group:has(.form-input:invalid:not(:placeholder-shown))::after { content: '✗'; color: #ef4444;}斑马纹表格#
.table { width: 100%; border-collapse: collapse;}
.table th,.table td { padding: 0.75rem 1rem; text-align: left; border-bottom: 1px solid #e5e7eb;}
.table th { background-color: #f9fafb; font-weight: 600;}
/* 斑马纹 */.table tbody tr:nth-child(odd) { background-color: #f9fafb;}
/* 悬停高亮 */.table tbody tr:hover { background-color: #f3f4f6;}
/* 首列加粗 */.table td:first-child { font-weight: 500;}
/* 最后一行无边框 */.table tbody tr:last-child td { border-bottom: none;}导航高亮#
.nav { display: flex; gap: 0.5rem;}
.nav-link { padding: 0.5rem 1rem; color: #6b7280; text-decoration: none; border-radius: 6px; transition: all 0.2s;}
.nav-link:hover { color: #1f2937; background-color: #f3f4f6;}
/* 当前页面 */.nav-link[aria-current='page'] { color: #3b82f6; background-color: #eff6ff;}
/* 或使用 :target */.nav-link:target { color: #3b82f6; background-color: #eff6ff;}响应式隐藏/显示#
/* 使用伪类和媒体查询组合 */.mobile-only { display: block;}
.desktop-only { display: none;}
@media (min-width: 768px) { .mobile-only { display: none; }
.desktop-only { display: block; }}
/* 空状态提示 */.list:empty::before { content: '暂无数据'; display: block; padding: 2rem; text-align: center; color: #9ca3af;}常见问题#
🤔 ::before 和 ::after 不显示?
- 必须有
content属性,哪怕是空字符串 - 检查父元素是否是替换元素(如
img、input、br),它们不支持伪元素
🤔
确保元素真的是父元素的第一个子元素。如果前面有空白文本节点或注释,可能会影响结果。考虑使用 :first-of-type。
🤔
触屏设备没有真正的”悬停”状态,可以用 :active 或 JavaScript 处理触摸事件。
伪类和伪元素是 CSS 选择器的强大扩展。合理使用它们可以减少 HTML 标记、增强交互体验、实现优雅的装饰效果。下一篇我们将学习现代选择器,如 :is()、:where() 和 :has()。