第06篇:伪类详解:状态与结构
伪类是 CSS 选择器中最灵活、最强大的一类。它们让你能够根据元素的状态(如鼠标悬停、获得焦点)或结构位置(如第一个子元素、奇数行)来选择元素,而无需修改 HTML。掌握伪类,意味着你可以用更少的代码实现更丰富的交互效果。
学习目标
- 掌握链接伪类(
:link、:visited、:hover、:active)及其使用顺序 - 掌握用户交互伪类(
:hover、:focus、:focus-visible、:active、:disabled) - 掌握表单状态伪类(
:checked、:indeterminate、:required、:valid、:invalid) - 掌握结构伪类(
:first-child、:last-child、:nth-child()、:nth-of-type()等) - 掌握现代伪类(
:is()、:where()、:not()、:has()) - 理解伪类的权重计算规则
核心知识点
一、什么是伪类?
伪类(Pseudo-class)用于选中处于特定状态或具有特定结构位置的元素。
/* 选中鼠标悬停时的按钮 */.btn:hover{background:#357abd;}/* 选中列表中的第一个子元素 */li:first-child{border-top:none;}/* 选中获得焦点的输入框 */input:focus{border-color:#4a90d9;}语法特征:以冒号:开头,紧跟伪类名称,如:hover、:nth-child(2n+1)。
与伪元素的区别:
| 伪类 | 伪元素 |
|---|---|
:单冒号 | ::双冒号(CSS3 规范) |
| 选中已有元素的特定状态 | 在元素前后创建虚拟内容 |
:hover、:nth-child() | ::before、::after |
二、链接伪类(Link Pseudo-classes)
专门用于<a>标签的四个伪类,被称为“LoVe HAte”规则:
/* L - :link - 未访问过的链接 */a:link{color:#4a90d9;}/* V - :visited - 已访问过的链接 */a:visited{color:#764ba2;}/* H - :hover - 鼠标悬停时 */a:hover{color:#e74c3c;text-decoration:underline;}/* A - :active - 鼠标按下瞬间 */a:active{color:#c0392b;}顺序很重要:必须按:link→:visited→:hover→:active的顺序书写,否则后面的规则会覆盖前面的。
记忆口诀:LoVe HAte(爱恨)
/* ✅ 正确顺序 */a:link{color:blue;}a:visited{color:purple;}a:hover{color:red;}a:active{color:orange;}/* ❌ 错误顺序:hover 在 link 前面,link 会覆盖 hover */a:hover{color:red;}/* 先定义 */a:link{color:blue;}/* 后定义,权重相同,会覆盖 hover */简化写法:现代浏览器中,直接写a和a:hover通常就够了:
/* 简洁实用的链接样式 */a{color:#4a90d9;text-decoration:none;transition:color 0.2s;}a:hover{color:#e74c3c;text-decoration:underline;}a:visited{color:#764ba2;/* 可选:区分已访问链接 */}三、用户交互伪类(User Action Pseudo-classes)
:hover— 鼠标悬停
/* 基础用法 */.btn:hover{background:#357abd;}/* 悬停时显示子元素(下拉菜单) */.dropdown:hover .dropdown-menu{display:block;}/* 悬停时放大图片 */.card:hover img{transform:scale(1.05);}/* 悬停时添加阴影 */.card:hover{box-shadow:0 8px 24pxrgba(0,0,0,0.15);}注意::hover在触摸屏设备上没有"悬停"概念,通常表现为点击时的瞬间状态。不要依赖:hover展示关键内容。
:focus— 获得焦点
当元素通过鼠标点击或键盘 Tab 键获得焦点时触发。
/* 输入框获得焦点时的样式 */input:focus, textarea:focus, select:focus{outline:none;/* 移除浏览器默认轮廓 */border-color:#4a90d9;box-shadow:0 0 0 3pxrgba(74,144,217,0.2);}/* 按钮获得焦点时的样式 */button:focus{outline:2px solid #4a90d9;outline-offset:2px;}:focus-visible— 仅在键盘聚焦时显示(现代浏览器支持)
/* 键盘 Tab 切换时显示轮廓,鼠标点击时不显示 */button:focus-visible{outline:2px solid #4a90d9;outline-offset:2px;}优势:解决了一个长期困扰——鼠标点击按钮时出现 ugly 的 outline,但键盘导航时又需要 outline。
:active— 激活状态
元素被鼠标按下但尚未释放的瞬间,或键盘 Enter/Space 按下时。
/* 按钮按下时的反馈 */.btn:active{transform:translateY(1px);box-shadow:inset 0 2px 4pxrgba(0,0,0,0.1);}:disabled— 禁用状态
/* 禁用状态的输入框和按钮 */input:disabled, button:disabled{background:#f0f0f0;color:#999;cursor:not-allowed;opacity:0.6;}四、表单状态伪类(Form State Pseudo-classes)
<formclass="demo-form"><inputtype="text"requiredplaceholder="必填项"><inputtype="email"placeholder="邮箱"><inputtype="checkbox"checked><inputtype="radio"name="gender"><buttontype="submit">提交</button></form>/* :checked - 选中的复选框/单选框 */input[type="checkbox"]:checked + label{color:#2ecc71;font-weight:bold;}/* :required - 必填字段 */input:required{border-left:3px solid #e74c3c;}/* :optional - 可选字段(非必填) */input:optional{border-left:3px solid #2ecc71;}/* :valid - 输入合法时 */input:valid{border-color:#2ecc71;}/* :invalid - 输入不合法时 */input:invalid{border-color:#e74c3c;}/* :placeholder-shown - 占位符显示时(未输入内容) */input:placeholder-shown{border-style:dashed;}/* :indeterminate - 不确定状态(如复选框的"半选"状态) */input[type="checkbox"]:indeterminate{background:#ffc107;}表单验证样式实战:
/* 优雅的表单验证反馈 */.form-field{margin-bottom:16px;}.form-field input{width:100%;padding:10px 14px;border:2px solid #ddd;border-radius:6px;transition:all 0.3s;}/* 未填写且获得焦点时 */.form-field input:focus:placeholder-shown{border-color:#ffc107;}/* 填写且合法 */.form-field input:valid{border-color:#2ecc71;}/* 填写且不合法 */.form-field input:focus:invalid{border-color:#e74c3c;}五、结构伪类(Structural Pseudo-classes)
结构伪类根据元素在 DOM 树中的位置来选择,无需添加类名。
基础结构伪类
/* :first-child - 第一个子元素 */li:first-child{border-top:none;}/* :last-child - 最后一个子元素 */li:last-child{border-bottom:none;}/* :only-child - 唯一的子元素 */.sidebar-widget:only-child{margin:0;}/* :empty - 没有子元素(包括文本)的元素 */.error-message:empty{display:none;/* 空错误提示自动隐藏 */}:nth-child()— 最强大的结构伪类
/* :nth-child(n) - 第 n 个子元素 */li:nth-child(3){background:#e3f2fd;/* 第三个 li 高亮 */}/* :nth-child(2n) 或 :nth-child(even) - 偶数项 */li:nth-child(even){background:#f5f5f5;}/* :nth-child(2n+1) 或 :nth-child(odd) - 奇数项 */li:nth-child(odd){background:#ffffff;}/* :nth-child(3n) - 每第 3 个 */.gallery-item:nth-child(3n){margin-right:0;}/* :nth-child(-n+3) - 前 3 个 */.top-list li:nth-child(-n+3){font-weight:bold;color:#e74c3c;}/* :nth-child(n+4) - 从第 4 个开始的所有 */.list li:nth-child(n+4){color:#999;}/* :nth-last-child(2) - 倒数第 2 个 */li:nth-last-child(2){border-bottom:2px solid #ffc107;}:nth-child()公式速查表:
| 公式 | 含义 | 选中项 |
|---|---|---|
nth-child(3) | 第 3 个 | 3 |
nth-child(even)/2n | 偶数 | 2, 4, 6, 8… |
nth-child(odd)/2n+1 | 奇数 | 1, 3, 5, 7… |
nth-child(3n) | 每第 3 个 | 3, 6, 9, 12… |
nth-child(3n+1) | 从 1 开始每第 3 个 | 1, 4, 7, 10… |
nth-child(-n+3) | 前 3 个 | 1, 2, 3 |
nth-child(n+4) | 从第 4 个开始 | 4, 5, 6, 7… |
nth-child(n+3):nth-child(-n+5) | 第 3 到第 5 个 | 3, 4, 5 |
:nth-of-type()— 按类型计数
:nth-child()计数时不区分元素类型,:nth-of-type()只统计同类型的兄弟元素。
<article><h2>标题 1</h2><p>段落 1</p><p>段落 2</p><h2>标题 2</h2><p>段落 3</p></article>/* ❌ 这样写不会选中 "段落 2",因为 h2 占了第 1 个位置 */article p:nth-child(2){color:red;}/* 实际上选中了 "段落 1",因为它是 article 的第 2 个子元素 *//* ✅ 用 :nth-of-type() 只数 p 元素 */article p:nth-of-type(2){color:red;/* 正确选中 "段落 2" */}/* 给每个 h2 后面的第一个 p 加样式 */article h2 + p{font-size:1.1em;}:nth-child()vs:nth-of-type()对比:
/* 场景:表格的斑马纹 */tr:nth-child(even){}/* 偶数行(包括 thead/tr) */tr:nth-of-type(even){}/* 同类型中的偶数行 *//* 场景:只给段落设置斑马纹 */article p:nth-of-type(odd){}/* ✅ 正确 */article p:nth-child(odd){}/* ❌ 可能被其他元素打乱 */六、现代伪类(Modern Pseudo-classes)
:not()— 否定伪类
选中不匹配选择器的元素。
/* 选中所有非 disabled 的按钮 */button:not(:disabled){cursor:pointer;}/* 选中所有不含 .active 类的 li */li:not(.active){opacity:0.7;}/* 选中不是第一个也不是最后一个的 li */li:not(:first-child):not(:last-child){margin:8px 0;}/* CSS4 支持传入选择器列表(现代浏览器) */li:not(:first-child, :last-child){margin:8px 0;}注意::not()不能嵌套伪元素,且内部的复杂选择器在现代浏览器才支持。
:is()— 匹配列表中的任意一个
简化复杂的选择器群组。
/* ❌ 冗长的写法 */h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover{color:#e74c3c;}/* ✅ 用 :is() 简化 */:is(h1, h2, h3, h4) a:hover{color:#e74c3c;}/* 减少重复 */article :is(h1, h2, h3){color:#1a1a2e;line-height:1.3;}/* 相当于: article h1, article h2, article h3 { color: #1a1a2e; line-height: 1.3; } */:is()与:where()的区别:
:is() | :where() | |
|---|---|---|
| 权重 | 取参数中最高的权重 | 权重永远为0 |
| 用途 | 简化选择器并保持权重 | 简化选择器且不增加权重 |
/* :is() 的权重 = .class (10) */:is(h1, .title, #header){}/* 权重 = 0-1-0 = 10 *//* :where() 的权重 = 0 */:where(h1, .title, #header){}/* 权重 = 0-0-0 = 0 */:has()— 父选择器(现代浏览器支持)
选中包含特定子元素的父元素——这是 CSS 长期以来的重大突破!
/* 选中包含 img 的 figure 元素 */figure:has(img){border:1px solid #ddd;padding:10px;}/* 选中包含 figcaption 的 figure */figure:has(figcaption){background:#f8f9fa;}/* 选中后面紧跟 ul 的 li(二级菜单项) */li:has(> ul){position:relative;}/* 选中后面紧跟 ul 的 li 的 a(有下拉菜单的导航项) */li:has(> ul) > a::after{content:" ▼";font-size:0.8em;}/* 表单验证:选中包含非法输入的字段容器 */.form-field:has(input:invalid) .error-msg{display:block;}七、伪类的优先级权重
伪类(如:hover、:nth-child())的权重等同于类选择器,即10。
/* 权重计算 */.btn:hover{}/* 10 + 10 = 20 */li:first-child{}/* 1 + 10 = 11 */input:focus:invalid{}/* 1 + 10 + 10 = 21 */:is(#header, .nav){}/* 取最高 = 100(ID 的权重) */:where(#header){}/* 权重 = 0 */动手练习
练习 1:导航栏悬停效果
实现一个导航栏,要求:
- 普通链接为灰色
- 鼠标悬停时变为蓝色并出现下划线动画(下划线从中间向两侧展开)
- 当前页面链接(
.active)为蓝色且始终显示下划线 - 已访问过的链接为紫色(但不超过 10% 的透明度区别)
练习 2:斑马纹表格
给定以下 HTML,用:nth-child实现斑马纹效果:
<tableclass="data-table"><thead><tr><th>姓名</th><th>年龄</th><th>城市</th></tr></thead><tbody><tr><td>张三</td><td>25</td><td>北京</td></tr><tr><td>李四</td><td>30</td><td>上海</td></tr><!-- 更多行... --></tbody></table>- tbody 中的行奇偶不同背景色
- 鼠标悬停时行高亮
- 第一列的文字加粗
练习 3::has()实战
用:has()实现以下效果(如果浏览器不支持,用其他方式回退):
- 包含图片的
.card有更大的上内边距 .form-group中有input:invalid时,对应的label变红色- 选中后面紧跟
.details的.summary并添加箭头图标
常见误区 ⚠️
| 误区 | 真相 |
|---|---|
“:hover在手机上也能用” | 触摸屏没有"悬停"概念,:hover在手机上表现不一致,不要依赖它展示关键内容 |
“:focus和:focus-visible是一回事” | :focus-visible只在键盘聚焦时触发,鼠标点击时不触发,更适合现代交互 |
“:nth-child(2n)和:nth-of-type(2n)效果一样” | 当父元素中有多种类型子元素时,两者结果完全不同 |
“:not()可以嵌套任何选择器” | :not()内部传统上只能写一个简单选择器,现代浏览器支持选择器列表 |
“:is()和:where()完全一样” | :is()取参数中最高权重,:where()权重永远为 0 |
“:visited可以设置任意样式” | 出于隐私保护,浏览器限制:visited只能设置color、background-color、border-color、outline-color |
“:has()可以无限嵌套” | :has()不能嵌套:has(),且性能开销较大,应谨慎使用 |
| “结构伪类会动态更新” | 当 DOM 变化时结构伪类会自动重新计算,不需要刷新页面 |
速查卡片 📋
链接伪类顺序(LoVe HAte)
a:link{}/* L - 未访问 */a:visited{}/* V - 已访问 */a:hover{}/* H - 悬停 */a:active{}/* A - 按下 */结构伪类速查
| 伪类 | 选中 |
|---|---|
:first-child | 第一个子元素 |
:last-child | 最后一个子元素 |
:only-child | 唯一的子元素 |
:nth-child(n) | 第 n 个子元素 |
:nth-last-child(n) | 倒数第 n 个子元素 |
:first-of-type | 同类型中的第一个 |
:last-of-type | 同类型中的最后一个 |
:nth-of-type(n) | 同类型中的第 n 个 |
:empty | 无子元素(含文本) |
:nth-child()公式速查
| 公式 | 效果 |
|---|---|
even/2n | 偶数项 |
odd/2n+1 | 奇数项 |
3n | 每第 3 个 |
-n+3 | 前 3 个 |
n+4 | 从第 4 个开始 |
现代伪类
| 伪类 | 作用 | 权重 |
|---|---|---|
:is() | 匹配列表中任意一个 | 取最高 |
:where() | 匹配列表中任意一个 | 0 |
:not() | 排除匹配的元素 | 取参数权重 |
:has() | 包含特定子元素的父元素 | 复杂 |
扩展阅读
- MDN: 伪类
- MDN: :nth-child()
- MDN: :has() — 父选择器
- MDN: :is()
- CSS-Tricks: :nth-child() recipes(英文)
- Can I Use :has() — 查询浏览器支持
📌配套代码:
- CODE/06/pseudo-class-states.html — 状态伪类交互演示
- CODE/06/pseudo-class-structure.html — 结构伪类布局演示
🎉下一步:进入 第07篇:伪元素详解,学习用
::before和::after创建虚拟内容。