跳到主要内容

CSS 选择器

选择器是 CSS 的核心概念。CSS 本质上只做两件事:选择元素、设置样式。选择器决定了"选中哪些元素",这是一切样式应用的前提。

理解选择器不仅是写出正确 CSS 的基础,更是理解 CSS 层叠规则、解决样式冲突的关键。本章将系统介绍各种类型的选择器,以及选择器背后的设计理念。

选择器的本质

为什么选择器如此重要?

打开浏览器开发者工具,在 Elements 面板中选中任意元素,右侧 Styles 面板会显示所有匹配该元素的 CSS 规则。这些规则之所以能匹配,就是因为选择器。

选择器的质量直接影响代码的可维护性:

  • 过于宽泛的选择器(如 div)可能影响意料之外的元素
  • 过于具体的选择器(如 #app .container .list .item .link)难以复用和覆盖
  • 合理的选择器应该恰好选中目标元素,不多也不少

选择器语法结构

/* 最简单的规则集 */
选择器 {
属性:;
}

/* 实例 */
p {
color: #333;
}

选择器可以是简单的一个标签名,也可以是多个选择器的复杂组合。CSS 规范定义了超过 60 种选择器和 5 种组合器,它们可以灵活组合使用。

基本选择器

基本选择器是最简单的选择器形式,它们通过元素的标签、类名、ID 或属性来直接匹配元素。

元素选择器(类型选择器)

元素选择器直接使用 HTML 标签名:

/* 选择所有段落 */
p {
line-height: 1.6;
}

/* 选择所有链接 */
a {
color: #3498db;
}

/* 选择所有图片 */
img {
max-width: 100%;
}

元素选择器的优先级权重为 0-0-1(ID-CLASS-TYPE 三列中的 TYPE 列)。它是最基础的选择器,优点是简单直接,缺点是无法区分同类元素。

类选择器

类选择器使用 . 前缀匹配 class 属性:

/* 选择所有带有 class="container" 的元素 */
.container {
width: 1000px;
margin: 0 auto;
}

/* 选择所有带有 class="btn" 的元素 */
.btn {
padding: 10px 20px;
border-radius: 4px;
}

多类名组合

一个元素可以有多个类名,用空格分隔:

<div class="card featured">...</div>
/* 匹配同时拥有 card 和 featured 类的元素 */
.card.featured {
border: 2px solid gold;
}

/* 分别定义样式 */
.card {
background: white;
}

.featured {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

类选择器的优先级权重为 0-1-0。它是最常用的选择器类型,因为它足够具体但又不会过于死板。

ID 选择器

ID 选择器使用 # 前缀匹配 id 属性:

/* 选择 id="header" 的元素 */
#header {
position: fixed;
top: 0;
width: 100%;
}

/* 选择 id="main-content" 的元素 */
#main-content {
padding: 20px;
}

ID 选择器的优先级权重为 1-0-0,比类选择器高一个量级。

关于 ID 选择器的争议

ID 选择器曾被认为是"最佳实践",但现在社区普遍推荐优先使用类选择器

  1. 优先级过高:ID 的优先级太高,导致样式难以覆盖
  2. 复用性差:ID 在页面中必须唯一,无法复用
  3. 增加耦合:ID 常用于 JavaScript 钩子和页面锚点,混用会增加代码耦合

实际上,ID 选择器更适合用于 JavaScript 选择元素,而非 CSS 样式。

ID 作为锚点

ID 有一个特殊用途——创建页面内链接:

<!-- 点击这个链接 -->
<a href="#section-intro">跳转到简介</a>

<!-- 会滚动到这里 -->
<h2 id="section-intro">简介</h2>

通配符选择器

* 选择器匹配所有元素:

/* 选择所有元素 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

/* 选择某元素内的所有元素 */
article * {
font-family: inherit;
}

通配符选择器的优先级权重为 0-0-0。它不增加优先级,但匹配范围广。

最佳实践

经典的 CSS Reset 片段使用通配符:

*,
*::before,
*::after {
box-sizing: border-box;
}

这确保所有元素(包括伪元素)都使用 border-box 盒模型。

属性选择器

属性选择器使用 [] 匹配具有特定属性或属性值的元素:

/* 有 disabled 属性的元素 */
[disabled] {
opacity: 0.5;
}

/* type="text" 的输入框 */
[type="text"] {
border: 1px solid #ccc;
}

/* href 以 "https" 开头 */
[href^="https"] {
color: green;
}

/* href 以 ".pdf" 结尾 */
[href$=".pdf"] {
padding-left: 20px;
background: url('pdf-icon.png') no-repeat left center;
}

/* class 包含 "icon" */
[class*="icon"] {
display: inline-block;
}

属性选择器完整语法

选择器含义示例
[attr]有该属性[disabled]
[attr="value"]属性值完全匹配[type="text"]
[attr~="value"]属性值包含(空格分隔的词)[class~="active"]
`[attr="value"]`属性值为 value 或以 value- 开头
[attr^="value"]属性值以 value 开头[href^="https"]
[attr$="value"]属性值以 value 结尾[href$=".pdf"]
[attr*="value"]属性值包含 value[class*="btn"]

大小写敏感控制

/* i 表示不区分大小写 */
[href$=".PDF" i] {
/* 匹配 .pdf, .PDF, .Pdf 等 */
}

/* s 表示区分大小写(默认行为) */
[href$=".PDF" s] {
/* 只匹配 .PDF */
}

属性选择器的优先级权重为 0-1-0,与类选择器相同。

组合选择器

组合选择器通过元素之间的关系来精确匹配目标。它们不增加优先级权重,但能让选择器更加精确。

后代选择器(空格)

选择某元素内部的所有指定后代元素(无论嵌套多深):

/* article 内的所有段落 */
article p {
line-height: 1.8;
}

/* nav 内的所有链接 */
nav a {
color: white;
text-decoration: none;
}
<article>
<p>直接子元素段落</p>
<div>
<p>嵌套的段落,也会被选中</p>
</div>
</article>

子选择器(>)

只选择直接子元素:

/* article 的直接子元素段落 */
article > p {
margin-top: 20px;
}
<article>
<p>会被选中:直接子元素</p>
<div>
<p>不会被选中:嵌套的段落</p>
</div>
</article>

后代选择器 vs 子选择器

后代选择器 article p 会匹配任意深度的嵌套,而子选择器 article > p 只匹配一层。子选择器更精确,性能也略好,因为它不需要遍历整个子树。

相邻兄弟选择器(+)

选择紧接在某元素后的第一个兄弟元素:

/* h1 后紧接的第一个段落 */
h1 + p {
font-size: 1.2em;
color: #666;
}
<h1>标题</h1>
<p>会被选中:紧跟在 h1 后面</p>
<p>不会被选中:不是第一个</p>

这个选择器常用于调整标题后面首段的样式,或消除列表首项的边距。

通用兄弟选择器(~)

选择某元素后面的所有兄弟元素:

/* h2 后面的所有段落 */
h2 ~ p {
color: #444;
}
<h2>标题</h2>
<p>会被选中</p>
<div>其他元素</div>
<p>也会被选中:只要在 h2 后面</p>

~+ 的区别在于:+ 只选择紧邻的一个,~ 选择后面所有的同级元素。

伪类选择器

伪类用于选择元素的特定状态或位置。它们以 : 开头,可以分为两大类:状态伪类和结构伪类。

状态伪类

状态伪类根据用户交互状态来选择元素:

/* 未访问的链接 */
a:link {
color: blue;
}

/* 已访问的链接 */
a:visited {
color: purple;
}

/* 鼠标悬停 */
a:hover {
color: red;
}

/* 被激活(点击瞬间) */
a:active {
color: orange;
}

/* 获得焦点 */
input:focus {
border-color: #3498db;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
}

链接伪类的顺序

一个经典的面试题:为什么链接伪类要按 LVHA 的顺序写?

a:link { }
a:visited { }
a:hover { }
a:active { }

这涉及到 CSS 层叠规则。这四个伪类优先级相同,所以后面的会覆盖前面的。一个链接可以同时处于多个状态(如已访问 + 悬停),顺序决定了哪个样式生效。LVHA 顺序确保:

  1. 默认状态显示 :link:visited
  2. 悬停时 :hover 覆盖默认样式
  3. 点击时 :active 覆盖悬停样式

结构伪类

结构伪类根据元素在 DOM 树中的位置来选择:

/* 第一个子元素 */
li:first-child {
border-top: none;
}

/* 最后一个子元素 */
li:last-child {
border-bottom: none;
}

/* 第 n 个子元素 */
tr:nth-child(odd) {
background: #f9f9f9; /* 奇数行 */
}

tr:nth-child(even) {
background: white; /* 偶数行 */
}

tr:nth-child(3n+1) {
/* 从第 1 个开始,每 3 个选一个 */
}

/* 倒数第 n 个 */
li:nth-last-child(2) {
/* 倒数第二个 */
}

/* 唯一子元素 */
p:only-child {
/* 只有一个子元素的 p */
}

/* 第一个同类型元素 */
p:first-of-type {
/* 同类型元素中的第一个 */
}

/* 第 n 个同类型元素 */
p:nth-of-type(2) {
/* 第二个 p 元素 */
}

nth-child 与 nth-of-type 的区别

这是一个常见的混淆点:

<div>
<h2>标题</h2>
<p>第一段</p>
<p>第二段</p>
</div>
/* nth-child:计算所有子元素中的位置 */
p:nth-child(2) {
/* 选中第一段:它是第二个子元素且是 p */
}

p:nth-child(3) {
/* 选中第二段 */
}

/* nth-of-type:只计算同类型元素 */
p:nth-of-type(1) {
/* 选中第一段:它是第一个 p 元素 */
}

p:nth-of-type(2) {
/* 选中第二段 */
}

nth-child(n) 先找第 n 个子元素,再检查是否匹配选择器;nth-of-type(n) 只在同类型元素中计算位置。

nth-child 公式解析

/* 关键字 */
:nth-child(odd) /* 奇数:1, 3, 5, 7... */
:nth-child(even) /* 偶数:2, 4, 6, 8... */

/* 数字 */
:nth-child(3) /* 第 3 个 */

/* 公式:an + b */
:nth-child(2n) /* 偶数,同 even */
:nth-child(2n+1) /* 奇数,同 odd */
:nth-child(3n) /* 3, 6, 9, 12... */
:nth-child(3n+1) /* 1, 4, 7, 10... */
:nth-child(n+3) /* 从第 3 个开始,选后面所有 */
:nth-child(-n+3) /* 前 3 个 */
:nth-child(3n-1) /* 2, 5, 8, 11... */

表单伪类

表单元素有专门的状态伪类:

/* 启用状态 */
input:enabled {
background: white;
}

/* 禁用状态 */
input:disabled {
background: #eee;
cursor: not-allowed;
}

/* 选中状态(复选框、单选框) */
input:checked {
outline: 2px solid #3498db;
}

/* 必填字段 */
input:required {
border-left: 3px solid red;
}

/* 可选字段 */
input:optional {
border-left: 3px solid transparent;
}

/* 验证通过 */
input:valid {
border-color: green;
}

/* 验证失败 */
input:invalid {
border-color: red;
}

/* 在范围内(用于 number 类型) */
input:in-range {
border-color: green;
}

/* 超出范围 */
input:out-of-range {
border-color: orange;
}

/* 只读 */
input:read-only {
background: #f5f5f5;
}

/* 可读写 */
input:read-write {
background: white;
}

否定伪类 :not()

:not() 排除匹配指定选择器的元素:

/* 不是 .intro 的段落 */
p:not(.intro) {
text-indent: 2em;
}

/* 不是禁用的按钮 */
button:not(:disabled) {
cursor: pointer;
}

/* 不是第一个子元素的列表项 */
li:not(:first-child) {
border-top: 1px solid #ddd;
}

/* 多重否定 */
div:not(.active):not(.hidden) {
display: block;
}

:not() 的优先级

:not() 本身不增加优先级,它的优先级取决于参数中选择器的优先级。

:not(p) { }           /* 优先级 0-0-1,等于元素选择器 */
:not(.class) { } /* 优先级 0-1-0,等于类选择器 */
:not(#id) { } /* 优先级 1-0-0,等于 ID 选择器 */

伪元素选择器

伪元素用于选择元素的特定部分,或创建不存在于 DOM 中的元素。它们以 :: 开头(CSS3 语法),但 :before:after 的单冒号写法也被广泛支持。

::before 和 ::after

这两个伪元素在元素内容的前后插入生成内容:

/* 必须有 content 属性 */
.quote::before {
content: """;
color: #999;
}

.quote::after {
content: """;
color: #999;
}

/* 添加序号 */
.step::before {
content: counter(step) ". ";
counter-increment: step;
}

/* 必填标记 */
.required::after {
content: "*";
color: red;
}

/* 清除浮动 */
.clearfix::after {
content: "";
display: table;
clear: both;
}

::before::after 创建的是行内元素,可以通过 display 属性改变。

其他伪元素

/* 首字母(用于首字下沉效果) */
p::first-letter {
font-size: 3em;
float: left;
line-height: 1;
margin-right: 0.1em;
}

/* 首行 */
p::first-line {
font-weight: bold;
}

/* 选中的文本 */
::selection {
background: #3498db;
color: white;
}

/* 输入框占位符 */
input::placeholder {
color: #999;
font-style: italic;
}

/* 滚动条(Webkit) */
::-webkit-scrollbar {
width: 8px;
}

::-webkit-scrollbar-track {
background: #f1f1f1;
}

::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}

现代伪类选择器

CSS Selectors Level 4 引入了一些强大的新选择器,它们正在改变我们编写 CSS 的方式。

:is() 匹配任意

:is() 接受一个选择器列表,匹配其中任意一个选择器:

/* 传统写法 */
header h1, header h2, header h3,
main h1, main h2, main h3,
article h1, article h2, article h3 {
color: #2c3e50;
}

/* 使用 :is() 简化 */
:is(header, main, article) :is(h1, h2, h3) {
color: #2c3e50;
}

容错性

:is() 是"宽容的"——如果列表中有一个选择器无效,其他选择器仍然生效:

/* :is() 会忽略无效选择器,继续匹配有效的 */
:is(.valid, :invalid-pseudo, #another) {
color: red;
}

:where() 零优先级

:where() 语法与 :is() 完全相同,但优先级永远是 0-0-0

:where(header, main, article) :where(h1, h2, h3) {
color: #2c3e50;
/* 优先级 0-0-0 */
}

使用场景

:where() 特别适合用于基础样式重置,因为零优先级意味着任何其他选择器都能覆盖它:

/* 基础样式:零优先级 */
:where(h1, h2, h3, h4, h5, h6) {
margin: 0;
font-weight: 600;
}

/* 个性化样式:优先级 0-0-1,轻松覆盖 */
h1 {
margin: 1rem 0;
}

:is() 和 :where() 的优先级规则

:is() 的优先级等于其参数中优先级最高的那个:

:is(#id, .class, div) {
/* 优先级为 1-0-0,因为 #id 最高 */
}

:where(#id, .class, div) {
/* 优先级始终为 0-0-0 */
}

利用 :is() 提高优先级

/* 当需要提高优先级但不想改变选择器结构时 */
.button:is(.button) {
/* 添加一个假的 :is() 来增加优先级 */
}

:has() 关系选择器

:has() 是 CSS 中长期缺失的"父选择器",它根据子元素或兄弟元素来选择父元素:

/* 选择包含 img 的 a 元素 */
a:has(img) {
display: block;
padding: 10px;
}

/* 选择包含 h2 的 section */
section:has(h2) {
border-left: 3px solid #3498db;
}

/* 选择其后紧跟 h2 的 h1 */
h1:has(+ h2) {
margin-bottom: 0.5rem;
}

/* 选择包含必填字段的表单组 */
.form-group:has(input:required) {
font-weight: bold;
}

逻辑组合

/* OR 逻辑:包含 a 或 b */
.card:has(.image, .video) {
/* 包含 image 或 video */
}

/* AND 逻辑:同时包含 a 和 b */
.card:has(.title):has(.description) {
/* 同时包含 title 和 description */
}

/* NOT 逻辑:不包含 */
article:not(:has(img)) {
/* 不包含图片的文章 */
}

实际应用示例

<!-- 根据内容动态调整布局 -->
<div class="card">
<img src="photo.jpg" alt="">
<h3>带图片的卡片</h3>
</div>

<div class="card">
<h3>纯文字卡片</h3>
</div>
.card {
padding: 20px;
border: 1px solid #ddd;
}

/* 包含图片的卡片使用横向布局 */
.card:has(img) {
display: grid;
grid-template-columns: 120px 1fr;
gap: 15px;
}

/* 不包含图片的卡片居中 */
.card:not(:has(img)) {
text-align: center;
}

:focus-visible:focus-within

这两个伪类解决了焦点样式的问题:

/* :focus-visible - 只在键盘导航时显示焦点环 */
button:focus {
/* 传统做法:所有情况都显示焦点样式 */
outline: 2px solid blue;
}

button:focus-visible {
/* 现代做法:仅在键盘导航时显示 */
outline: 2px solid #3498db;
outline-offset: 2px;
}

/* 鼠标点击时不显示焦点环 */
button:focus:not(:focus-visible) {
outline: none;
}

/* :focus-within - 子元素获得焦点时父元素响应 */
.form-group:focus-within {
background-color: #f0f7ff;
border-color: #3498db;
}

/* 下拉菜单展开 */
.nav-item:focus-within .dropdown {
display: block;
}

:placeholder-shown

选择正在显示占位符文本的输入框:

/* 输入框为空时 */
input:placeholder-shown {
border-color: #ddd;
}

/* 输入框有内容时 */
input:not(:placeholder-shown) {
border-color: #3498db;
}

/* 浮动标签效果 */
.float-label {
position: relative;
}

.float-label label {
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
transition: all 0.2s;
pointer-events: none;
color: #999;
}

.float-label input:not(:placeholder-shown) + label,
.float-label input:focus + label {
top: 0;
transform: translateY(-50%);
font-size: 12px;
background: white;
padding: 0 5px;
color: #3498db;
}

选择器优先级详解

当多个 CSS 规则作用于同一个元素时,浏览器通过优先级算法决定应用哪个样式。理解优先级是解决样式冲突的关键。

优先级的本质

优先级是一个三列的数值:ID-CLASS-TYPE,比较时从左到右逐列比较。

包含的选择器每个的权重
ID 列ID 选择器 #id1-0-0
CLASS 列类选择器、属性选择器、伪类0-1-0
TYPE 列元素选择器、伪元素0-0-1
无权重通配符 *:where()0-0-0

计算示例

/* 计算过程 */
div { } /* 0-0-1 */
div::first-letter { } /* 0-0-2(1 元素 + 1 伪元素) */
.nav { } /* 0-1-0 */
.nav:hover { } /* 0-2-0(1 类 + 1 伪类) */
#header { } /* 1-0-0 */
#header .nav { } /* 1-1-0 */
#header .nav a { } /* 1-1-1 */
#header .nav a:hover { } /* 1-2-1 */
body #app .container .list .item {/* 1-3-1,但很糟糕 */

比较规则

1-0-0 > 0-99-99  (ID 列大的胜出,无论其他列)
0-2-0 > 0-1-99 (CLASS 列大的胜出)
0-0-3 > 0-0-2 (TYPE 列大的胜出)

常见陷阱

陷阱一:ID 选择器难以覆盖

/* 场景:需要覆盖某个样式 */
#header .nav .item {
color: blue; /* 优先级 1-2-0 */
}

/* 尝试覆盖 */
.nav .item.active {
color: red; /* 优先级 0-2-0,失败! */
}

/* 错误做法:使用 !important */
.nav .item.active {
color: red !important; /* 能工作,但难以维护 */
}

/* 正确做法:提高优先级 */
#header .nav .item.active {
color: red; /* 优先级 1-3-0 */
}

/* 更好的做法:原始选择器就不该用 ID */
.header .nav .item {
color: blue; /* 优先级 0-2-0 */
}

.nav .item.active {
color: red; /* 优先级 0-2-0,顺序靠后,成功! */
}

陷阱二:过度嵌套

/* 糟糕的选择器 */
body #app .main-content .sidebar .widget .title {
font-size: 18px;
}

/* 问题:
* 1. 优先级过高(1-1-4)
* 2. 依赖 HTML 结构
* 3. 难以复用
*/

/* 更好的做法 */
.widget-title {
font-size: 18px;
}

陷阱三:组合器不增加优先级

/* 这三个选择器优先级相同,都是 0-0-1 */
div { color: red; }
body > div { color: blue; } /* 组合器 > 不增加权重 */
html body div { color: green; } /* 后声明,生效 */

特殊情况

内联样式

内联样式的优先级高于所有选择器,可以理解为 1-0-0-0

<div id="header" class="nav" style="color: red;">
<!-- 即使有 #header { color: blue; },这里文字仍是红色 -->
</div>

!important

!important 打破正常优先级规则,具有最高优先级:

p {
color: red !important; /* 覆盖几乎所有规则 */
}
慎用 !important

!important 会破坏 CSS 的可维护性。只有以下情况可以考虑使用:

  1. 覆盖第三方库的样式
  2. 内联样式的覆盖(配合 ID 选择器)
  3. 工具类(如 .hidden { display: none !important; }

使用时务必添加注释说明原因。

优先级调试技巧

  1. 使用开发者工具:在 Styles 面板中,被划掉的规则表示被更高优先级覆盖
  2. 检查选择器:点击选择器查看具体是哪个部分在匹配
  3. 使用 filter:在 Computed 面板中过滤属性,查看最终值

选择器性能考量

虽然现代浏览器对选择器匹配进行了大量优化,但在编写大型项目时仍需注意性能。

原则:从右向左匹配

CSS 选择器的匹配是从右向左进行的:

.nav ul li a {
color: blue;
}

浏览器的工作流程:

  1. 先找出页面上所有 <a> 元素
  2. 检查每个 <a> 是否在 <li>
  3. 检查是否在 <ul>
  4. 检查是否在 .nav

这意味着选择器最右边的关键部分决定了初始匹配范围。

性能最佳实践

避免过于宽泛的起始选择器

/* 糟糕:从 * 开始,需要检查所有元素 */
* [data-tooltip] { }

/* 较好:限定范围 */
.button[data-tooltip] { }

减少嵌套层级

/* 糟糕:多层嵌套 */
.header .nav .menu .item .link { }

/* 更好:使用类名直接选择 */
.nav-link { }

避免使用通配符作为关键选择器

/* 糟糕 */
[class*="icon-"] { }

/* 较好 */
.icon { }

优先使用类选择器和 ID 选择器

它们比属性选择器和部分伪类更快。

:has() 的性能考虑

:has() 需要浏览器在 DOM 变化时重新评估匹配,因此要注意:

/* 避免:过于宽泛的锚点 */
body:has(.foo) { }
*:has(.bar) { }

/* 推荐:具体的锚点 */
.card:has(img) { }
.form-group:has(input:focus) { }

/* 避免:深层嵌套检查 */
.ancestor:has(.deep .nested .element) { }

/* 推荐:使用子选择器限制范围 */
.card:has(> img) { }

浏览器兼容性

现代选择器在主流浏览器中得到了良好支持:

选择器ChromeFirefoxSafariEdge
:is()88+78+14+88+
:where()88+78+14+88+
:has()105+121+15.4+105+
:focus-visible86+85+15.4+86+
:focus-within60+52+10.1+79+

渐进增强策略

对于需要支持旧浏览器的情况:

/* 使用 @supports 检测 */
@supports selector(:has(*)) {
.card:has(.featured) {
border: 2px solid gold;
}
}

/* 回退方案 */
.card.featured {
border: 2px solid gold;
}

实战技巧总结

命名约定

/* 使用语义化的类名 */
.card { } /* 组件 */
.card-header { } /* 组件部分 */
.card--featured { } /* 修饰符 */
.is-active { } /* 状态类 */

选择器策略

  1. 优先使用类选择器,避免 ID 选择器和过度嵌套
  2. 保持低优先级,让样式易于覆盖
  3. 使用 :where() 编写基础样式,零优先级便于扩展
  4. 合理使用现代选择器,简化代码但注意性能

常见模式

/* 卡片组件 */
.card { }
.card:hover { }
.card.featured { }
.card:has(img) { }

/* 导航 */
.nav-link { }
.nav-link:hover { }
.nav-link.active { }
.nav-item:has(.active) { }

/* 表单 */
.input { }
.input:focus { }
.input:invalid { }
.form-group:has(.input:focus) { }

/* 列表 */
.list-item { }
.list-item:first-child { }
.list-item:last-child { }
.list-item:nth-child(odd) { }

小结

选择器是 CSS 的基础,掌握它意味着:

  1. 理解基本选择器:元素、类、ID、属性、通配符
  2. 善用组合选择器:后代、子、兄弟
  3. 掌握伪类和伪元素:状态、结构、生成内容
  4. 拥抱现代选择器:is():where():has()
  5. 理解优先级规则:ID-CLASS-TYPE 计算方法
  6. 注意性能和兼容性:写出高效、可维护的代码

练习

  1. 计算以下选择器的优先级:

    • #header .nav li a
    • .container > .item.active
    • body.main #content article p:first-child
    • :where(#header) .nav
  2. 使用 :has() 实现以下效果:

    • 包含图片的链接添加内边距
    • 有错误消息的表单组显示红色边框
    • 被激活的导航项的父元素添加背景
  3. 重构以下选择器,使其更简洁且优先级更低:

    • body #app .main .content .article .title
    • .container .row .col .card .card-body .text
  4. 使用 :is():where() 简化:

    • h1, h2, h3, h4, h5, h6
    • input:focus, textarea:focus, select:focus