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 的优先级太高,导致样式难以覆盖
- 复用性差:ID 在页面中必须唯一,无法复用
- 增加耦合: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 顺序确保:
- 默认状态显示
:link或:visited - 悬停时
:hover覆盖默认样式 - 点击时
: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 选择器 #id | 1-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 会破坏 CSS 的可维护性。只有以下情况可以考虑使用:
- 覆盖第三方库的样式
- 内联样式的覆盖(配合 ID 选择器)
- 工具类(如
.hidden { display: none !important; })
使用时务必添加注释说明原因。
优先级调试技巧
- 使用开发者工具:在 Styles 面板中,被划掉的规则表示被更高优先级覆盖
- 检查选择器:点击选择器查看具体是哪个部分在匹配
- 使用
filter:在 Computed 面板中过滤属性,查看最终值
选择器性能考量
虽然现代浏览器对选择器匹配进行了大量优化,但在编写大型项目时仍需注意性能。
原则:从右向左匹配
CSS 选择器的匹配是从右向左进行的:
.nav ul li a {
color: blue;
}
浏览器的工作流程:
- 先找出页面上所有
<a>元素 - 检查每个
<a>是否在<li>内 - 检查是否在
<ul>内 - 检查是否在
.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) { }
浏览器兼容性
现代选择器在主流浏览器中得到了良好支持:
| 选择器 | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
:is() | 88+ | 78+ | 14+ | 88+ |
:where() | 88+ | 78+ | 14+ | 88+ |
:has() | 105+ | 121+ | 15.4+ | 105+ |
:focus-visible | 86+ | 85+ | 15.4+ | 86+ |
:focus-within | 60+ | 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 { } /* 状态类 */
选择器策略
- 优先使用类选择器,避免 ID 选择器和过度嵌套
- 保持低优先级,让样式易于覆盖
- 使用
:where()编写基础样式,零优先级便于扩展 - 合理使用现代选择器,简化代码但注意性能
常见模式
/* 卡片组件 */
.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 的基础,掌握它意味着:
- 理解基本选择器:元素、类、ID、属性、通配符
- 善用组合选择器:后代、子、兄弟
- 掌握伪类和伪元素:状态、结构、生成内容
- 拥抱现代选择器:
:is()、:where()、:has() - 理解优先级规则:ID-CLASS-TYPE 计算方法
- 注意性能和兼容性:写出高效、可维护的代码
练习
-
计算以下选择器的优先级:
#header .nav li a.container > .item.activebody.main #content article p:first-child:where(#header) .nav
-
使用
:has()实现以下效果:- 包含图片的链接添加内边距
- 有错误消息的表单组显示红色边框
- 被激活的导航项的父元素添加背景
-
重构以下选择器,使其更简洁且优先级更低:
body #app .main .content .article .title.container .row .col .card .card-body .text
-
使用
:is()或:where()简化:h1, h2, h3, h4, h5, h6input:focus, textarea:focus, select:focus