CSS 教程 / CSS 组合符与伪类:按结构、状态和位置选择元素
组合符(combinator)根据 HTML 结构中元素的关系进行选择,而伪类选择状态或结构条件。子选择器与后代选择器不同,:first-child 指的是第一个子元素,而不是特定类型的第一个元素。
阅读父元素、子元素、后代元素和兄弟元素的关系
在以下结构中,section 是 h2、段落 A、div 和段落 C 的父元素。段落 B 是 div 的子元素,而 div 是 section 的后代。A 和 C 是兄弟元素,因为它们有相同的父元素,但 A 和 B 不是兄弟元素,因为它们有不同的父元素。HTML 结构比在屏幕上看起来接近与否更重要。
<section class="garden">
<h2>Visitor Information</h2>
<p>A: Operating hours</p>
<div>
<p>B: Greenhouse Guide</p>
</div>
<p>C: What you need</p>
</section>
section.garden
├─ h2
├─ p ← A
├─ div
│ └─ p ← B
└─ p ← C
| 关系符号 | 选择器 | 选中的段落 | 未选中的段落 |
|---|---|---|---|
| 空格:后代 | .garden p |
A、B、C | section 外的 p |
| >: 直接子元素 | .garden > p |
A, C | B 位于 div 内部,与 section 之间多了一层 |
| +: 下一个兄弟元素 | h2 + p |
A | B 有不同的父元素,并且在 C 之前有一个 div。 |
| ~: 后续兄弟元素 | h2 ~ p |
A, C | B 的父元素不同于 h2。 |
组合器通常针对其右侧的元素。h2 + p 将紧跟在 h2 之后作为兄弟的 p 进行样式设置。缩进或换行产生的空白文本节点不会中断此元素-兄弟关系。更多详情,请参阅 基本 CSS 选择器:类型、类、ID 和属性。
比较后代选择器和子选择器
.garden p { color: #087f5b; }
.garden > p { border-left: 4px solid #087f5b; padding-left: 12px; }
第一个规则让 garden 中的所有 p 元素变成绿色。即使中间有一个 div,它也包含 B。第二个规则只给直接子元素 A 和 C 添加左边框和左内边距。由于 color 和 border-left 是不同的属性,A 和 C 可以同时拥有绿色和边框。
后代选择器适合在整个部分中共享段落样式。直接子选择器可以排除嵌套卡片中的段落。结构变化会影响这些匹配;避免串联不必要的子组合符,这会让样式表依赖于固定的深度。参见 MDN:CSS 组合符。
比较相邻和后续兄弟
h2 + p { background-color: #fff4cc; }
h2 ~ p { font-style: italic; }
第一条规则给段落 A(标题的下一个同级元素)设置黄色背景。第二条规则将 A 和 C 设为斜体,因为它们都是 h2 的后续 p 同级元素。B 有不同的父元素,因此不匹配。这两种兄弟组合符均不选择前面的兄弟元素。
如果你在 h2 和 A 之间插入 <div>Additional notice</div>,黄色背景将消失,因为 A 不再是紧邻的同级元素。然而,它仍然跟随 h2 且在相同父元素中的条件成立,所以斜体仍然存在。看到这个差异有助于理解 + 与 ~ 各自的匹配条件。
使用伪类选择状态和位置
a:hover { background-color: #fff4cc; }
a:focus-visible { outline: 3px solid #1d4ed8; outline-offset: 3px; }
.plants li:first-child { font-weight: bold; }
.plants li:nth-child(odd) { background-color: #e9f7ef; }
:hover 选择指针悬停的状态。:focus-visible 选择浏览器认为需要显示焦点的状态,这对于在键盘上通过 Tab 键移动焦点时进行检查非常有用。:focus 选择焦点状态本身,因此两者的含义并不完全相同。此示例指定了一个额外的轮廓,而不会移除默认的焦点指示。你可以继续在 CSS 链接状态和可见键盘焦点 的实际链接中练习如何按状态区分颜色和焦点。
当你将鼠标悬停在链接上时,背景会变成黄色;当你使用 Tab 键切换到链接时,会出现蓝色轮廓。在触摸环境中,无法保证用户始终能体验到悬停效果。请确保重要信息,例如营业时间或链接名称,默认情况下仍然可读。
:first-child 选择其父元素的第一个子元素。:nth-child(odd) 选择子元素顺序为奇数的情况,例如 1、3 或 5。完整示例中的植物列表只有 li 元素,因此编号为 1 的薄荷会变为粗体,并为编号 1 和 3 的项目创建背景。如果使用 even 而不是 odd,则编号 2 和 4 是目标。
p:first-child 意思是“既是 p 元素,又是其父元素的第一个子元素”,而不是“第一个 p 元素”。在此示例中,section 的第一个子元素是 h2,因此 .garden > p:first-child 不匹配任何段落。nth-child 会计算所有同级元素,而不仅仅是相同标签的元素。当你需要在同一类型元素中指定位置时,可以使用 nth-of-type 条件。MDN 伪类参考 解释了这些状态和位置条件。
运行完整示例
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Small Garden</title>
<style>
body { margin: 24px; font-family: sans-serif; line-height: 1.7; }
.garden p { color: #087f5b; }
.garden > p { border-left: 4px solid #087f5b; padding-left: 12px; }
h2 + p { background-color: #fff4cc; }
h2 ~ p { font-style: italic; }
.plants li:first-child { font-weight: bold; }
.plants li:nth-child(odd) { background-color: #e9f7ef; }
a:hover { background-color: #fff4cc; }
a:focus-visible { outline: 3px solid #1d4ed8; outline-offset: 3px; }
</style>
</head>
<body>
<h1>Small Garden</h1>
<section class="garden">
<h2>Visitor Information</h2>
<p>A: We open at 9am every day.</p>
<div>
<p>B: Please observe quietly in the greenhouse.</p>
</div>
<p>C: Bring a water bottle and comfortable shoes.</p>
</section>
<ul class="plants">
<li>1: Mint</li>
<li>2: Basil</li>
<li>3: Rosemary</li>
<li>4: Time</li>
</ul>
<a href="#contact">Go to the contact section</a>
<h2 id="contact">Contact</h2>
<p>Ask at the information desk.</p>
</body>
</html>
当你保存并运行时,A·B·C 是绿色的,且只有 A·C 有左边框。A 有黄色背景,而 A·C 的文字是斜体。植物列表的第一项是加粗的,且奇数项有浅色背景。分别使用鼠标和键盘检查链接。段落前的字母 A~C 是直接写在文本中的字母,用于比较选择结果。完整示例中的 h2 + p 和 h2 ~ p 没有限定在 .garden 内,因此也会为页面底部 Contact 标题后的段落添加背景和斜体;A、B、C 的比较针对的是 garden 区域。
更改结构并测试每个条件
- 在 h2 和 A 之间添加一个 div。验证只有 A 的背景消失,斜体和边框仍在。练习后,删除添加的 div。
- 通过仅删除包围段落 B 的 div 标签,使 B 成为 section 的直接子元素。确认 B 也出现左边框和斜体。在 garden 区域内,+ 条件仍然只匹配 A。确认后,恢复 div。
- 将
:nth-child(odd)替换为:nth-child(even)。检查列表背景是否从数字 1 和 3 移动到数字 2 和 4,并且第 1 项由 first-child 设置的粗体仍然保留。 - 将鼠标移离链接,点击页面内部,然后使用 Tab 键切换到链接。确保焦点轮廓可见,并且可以通过 Enter 键移动到联系部分。如果浏览器地址栏获得焦点,先返回页面。
请分别检查每个条件。仅改变颜色值以扩大选择范围或在键盘焦点问题上添加悬停不会解决根本原因。可以在开发者工具中强制启用状态来辅助比较,再通过实际的 Tab 键移动和鼠标悬停确认用户能看到的结果。




