开始根据林想的Web工坊大佬的建议路线学习CSS。
本文涉及内容会如下:
CSS cascading and inheritance - CSS | MDN
CSS Selectors and Combinators
CSS 有 80+ 个选择器和组合器,按类型分为以下几类。
Basic Selectors(基础选择器)
| 选择器 | 符号 | 说明 | 示例 |
|---|---|---|---|
| Type selector | 标签名 | 选中所有给定节点名的元素 | div 选中所有 <div>,input 选中所有 <input> |
| Universal selector | * | 特殊的 type selector,选中所有元素 | * 选中页面全部元素 |
| Class selector | .类名 | 选中所有 class 属性匹配的元素 | .index 选中 class="index" 的元素 |
| ID selector | #id名 | 选中 id 属性匹配的元素 | #toc 选中 id="toc" 的元素 |
id在文档中应唯一,但如果有重复,ID selector 会匹配所有同 id 元素。
Compound selector(复合选择器)规则:将 type/universal 与 class/id 组合时,type/universal 必须在前面。
1
2
3
4
5
* { font-style: italic; } /* universal */
p { color: red; } /* type */
.myClass { text-decoration: underline; } /* class */
#myId { font-family: monospace; } /* id */
p.myClass#myId { font-size: 1.5rem; } /* compound */
Combinators(组合器)
组合器将选择器连接,基于 DOM 树中的关系选中元素,构成 complex selector(复杂选择器)。
只有后代组合器和子代组合器常用,其余不常用。
1. Descendant combinator(后代组合器)
- 符号:空格(一个或多个)
- 含义:选中第一个元素的所有后代节点
- 示例:
div span→ 选中<div>内所有<span>(无论嵌套多深)
2. Child combinator(子代组合器)
- 符号:
> - 含义:只选中第一个元素的直接子节点
- 示例:
div > span→ 只选中<div>的直接子<span>,不选更深层
3. Subsequent-sibling combinator(后续兄弟组合器)
- 符号:
~ - 含义:
A ~ B选中与 A 同父、且在 A 之后的所有 B - 示例:
h2 ~ p→ 选中<h2>之后所有的 ``(不必紧邻)
4. Next-sibling combinator(相邻兄弟组合器)
- 符号:
+ - 含义:
A + B只选中紧接在 A 之后的 B(同父) - 示例:
h2 + p→ 只选中紧跟<h2>的那一个``
5. Column combinator(列组合器)
- 符号:
|| - 含义:选中属于某个列作用域的节点
- 示例:
col || td→ 选中属于<col>作用域的<td>
6. Namespace separator(命名空间分隔符)
- 符号:
| - 含义:将 type selector / universal selector 限定到特定命名空间
- 示例:定义
@namespace SVG url('http://www.w3.org/2000/svg');后,SVG|a只匹配 SVG 内的链接,不匹配 HTML 的<a>
基本没人用,大多数网页只有一个命名空间(HTML),不需要区分。除非在写内联 SVG + CSS 混排那种极端场景。
1
2
3
4
5
6
7
/* 第一步:声明命名空间 */
@namespace svg url('http://www.w3.org/2000/svg');
@namespace html url('http://www.w3.org/1999/xhtml');
/* 第二步:用 | 限定选择器的作用域 */
svg|a { stroke: red; } /* 只选 SVG 里的 <a> */
html|a { color: blue; } /* 只选 HTML 里的 <a> */
CSS Nesting(嵌套写法)
刚出的新特性(2023 年底才主流浏览器全面支持),这个特性目前太新,用的人不多,还是多用Sass和Taiwind等,但未来会是主流。逻辑就是把后代/兄弟选择器写成嵌套结构,和 Sass/SCSS 当初干的事一样,但现在原生 CSS 直接支持了。
1
2
3
4
5
h2 + p ~ p { font-style: italic; } /* h2 紧邻的 p 之后的所有 p */
h2 + p + p { color: red; } /* h2 紧邻的 p 再紧邻的 p */
.myClass + p { text-decoration: underline; } /* 紧接 myClass 的 p */
#myId > .myClass { outline: 3px dashed red; } /* #myId 的直接子 .myClass */
* > p { font-size: 1.1rem; } /* 任何直接子 p */
以上复杂选择器可用 CSS nesting + & 嵌套选择器实现等价写法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
h2 {
& + p {
& ~ p { font-style: italic; }
& + p { color: red; }
}
}
.myClass {
& + p { text-decoration: underline; }
}
#myId {
& > .myClass { outline: 3px dashed red; }
}
* {
& > p { font-size: 1.1rem; }
}
Attribute Selectors(属性选择器)
选中具有特定属性或属性值匹配子串的元素。
| 写法 | 含义 |
|---|---|
[type] | 选中所有设置了 type 属性的元素(无论值是什么) |
[type="submit"] | 选中 type="submit" 的元素 |
大小写规则:HTML 枚举属性(如
type)值匹配默认不区分大小写;非枚举属性(如class、id、data-*、role、aria-*)默认区分大小写,可加i修饰符改为不区分。
Pseudo-class Selectors(伪类选择器)
- 符号:单冒号
: - 定义:基于文档树中不包含的状态信息选中元素
- 数量:60+ 个伪类
伪类不是真正的 class,但表现得像 class 一样能选中元素,选中的是元素的特定状态,纯 CSS 的状态响应。
1
2
:target { ... } /* URL 含片段标识符时选中目标元素 */
a:visited { ... } /* 选中用户已访问过的 <a> */
伪类分类:
| 类别 | 说明 |
|---|---|
| Element display state | 元素显示状态(如 :fullscreen) |
| Input | 输入状态(如 :checked、:disabled) |
| Linguistic | 语言相关(如 :lang()) |
| Location | 位置相关(如 :target、:visited) |
| Resource state | 资源状态(如 :playing) |
| Time-dimensional | 时间维度(如 :current) |
| Tree-structural | 树结构(如 :first-child、:nth-child()) |
| User action | 用户操作(如 :hover、:focus) |
| Functional | 函数式(如 :is()、:not()、:has()) |
伪类是 simple selector,可与 type/universal 组成 compound selector,伪类须跟在 type/universal 后面。
Pseudo-element Selectors(伪元素选择器)
伪元素选择器可以通过CSS凭空创造新的虚拟元素,这个元素在HTML不实际存在。通过
::before和::after在元素前后插入内容非常便捷。伪元素的的规则存在于 CSSOM 里,伪元素本身的节点在渲染阶段由渲染引擎根据 CSSOM 规则生成。也因此通过伪元素插入的 content 是无法通过 JS 选中以及修改的,但通过
getComputedStyle()可以读到伪元素的计算样式。可以在 DevTools Elements 面板看到。
getComputedStyle()读伪元素时要传第二个参数:
1 2 3 4 5 6 // 语法 window.getComputedStyle(element, '伪元素名') // 实际写法 const style = window.getComputedStyle(document.querySelector('.box'), '::before') style.content // 读到 content 的值因为不涉及 JS,是一种轻量化的高效的插入装饰性内容的方法。
- 符号:双冒号
:: - 定义:代表 HTML 中不存在的实体
- 所属模块:CSS pseudo-elements module(非 selectors module)
1
2
::marker { ... } /* 选中列表项的标记符号 */
p::first-line { ... } /* 选中 p 的第一行 */
CSS Selector Structure
“选择器”可以指 simple selector、compound selector 或 complex selector。当作为 :has() 的参数时,称为 relative selector。多个选择器可用逗号组合成 selector list。
Simple Selector(简单选择器)
只有一个组件的选择器,不与其他选择器组件或组合器搭配使用。元素匹配 simple selector = 该选择器准确描述了这个元素。
包含以下任一即为 simple selector:
- Basic selector(type / universal / class / id)
- Attribute selector
- Pseudo-class
- Pseudo-element
1
2
#myId { } /* ID selector → simple selector */
[pattern*="\d"] { } /* attribute selector → simple selector */
Compound Selector(复合选择器)
多个 simple selector 不加组合器直接连写,表示对同一个元素同时满足多个条件。元素匹配 compound selector = 元素匹配其中所有 simple selector。
1
2
a#selected { } /* 是 <a> 且 id="selected" */
[type="checkbox"]:checked:focus { } /* type 为 checkbox + 已勾选 + 获焦 */
规则:
- Type selector 或 universal selector 必须放在最前面
- 整个序列中只允许一个 type selector 或 universal selector
- 不能有空格,因为空格就是 descendant combinator,一加空格就变成 complex selector 了
Complex Selector(复杂选择器)
一个或多个 simple / compound selector 用组合器(包括空格 descendant combinator)连接而成,表示对一组元素同时满足的条件关系。
1
2
a#selected > .icon { } /* id="selected" 的 <a> 的直接子 .icon */
.box h2 + p { } /* .box 后代中的 <h2> 紧邻的 */
阅读方向:选择器从右往左读。
a#selected > .icon→ 匹配所有 class 为 icon 的元素,且它们是 id=“selected” 的<a>的直接子元素.box h2 + p→ 匹配紧跟<h2>的 ``,且该<h2>是 .box 的后代
Selector List(选择器列表)
逗号分隔的 simple / compound / complex selector 列表。元素匹配 selector list = 元素匹配其中至少一个选择器。
1
2
#main,
article.heading { } /* 匹配 #main 或 article.heading */
Non-forgiving selector list(不容错列表)
选择器列表是默认不容错的,只有
:is()、:where()、:has()内部的列表是容错的。
如果列表中任何一个选择器无效,整个列表失效:
1
2
3
#main,
:bad-pseudoclass, /* ← 无效伪类 */
.validClass { /* 整个样式块都被作废! */ }
Forgiving selector list(容错列表)
用 :is() 或 :where() 包裹,无效选择器会被忽略,其余仍然生效:
1
2
:is(#main, :bad-pseudoclass, .validClass) { }
/* :bad-pseudoclass 被跳过,#main 和 .validClass 仍然生效 */
:is() 和 :where() 效果是类似的,但特异性不同, :is() 会选择列表中涉及规则的最高特异性,从而可能影响后续的规则覆盖,而 :where() 会强制特异性归零,从而不污染规则特异性。还有个类似的方法是 :has(),规范上 :has() 接收的是 relative selector(相对选择器),不是标准的 selector list。
Relative Selector(相对选择器)
以组合器开头的选择器,表示相对于一个或多个锚点元素的关系。如果没有显式组合器开头,则隐含一个 descendant combinator。
实际写 CSS 时,没有前缀的
:has()几乎不会单独出现在 CSS 规则里。
不能用在 selector list 中,只能用在特定上下文(如 :has())内。
1
2
3
4
5
6
7
8
/* :has:选祖先,基于后代判断 */
a:has(img) { } /* 我(a元素)里面包含 img 吗?包含就选中我 */
.card:has(.badge) { } /* 我(.card)里面有 .badge 吗?有就选中我 */
/* 任何函数式伪类不写前缀,默认前缀就是 * */
:has(+ div#topic > #reference) { } /* 有紧邻的 div#topic 子代 #reference */
:has(> .icon) { } /* 有直接子 .icon(隐含锚点为自身) */
dt:has(+ img) ~ dd { } /* 有紧邻 img 的 dt 的后续兄弟 dd */
Privacy and :visited
早期 :visited 选择器存在严重的隐私风险:网站可以通过 window.getComputedStyle() 等 JS 手段检测链接的已访问状态,从而获取用户的浏览历史,甚至推测用户身份。这个过程极快,几毫秒就能扫描上千个链接。
为了保护隐私,现代浏览器做了两层限制:
Little white lies
| 手段 | 浏览器的行为 |
|---|---|
window.getComputedStyle() | 永远返回未访问状态的值,不管链接是否真的被访问过 |
element.querySelector() | 同上,始终返回未访问状态 |
兄弟选择器 :visited + span | 相邻元素(如 span)按未访问状态渲染 |
| 嵌套链接 | 如果匹配的元素与被检测的链接不是同一个,按未访问状态渲染 |
这样处理后,JS 层面完全无法检测链接是否被访问过。
Limits to visited link styles(已访问链接样式的限制)
只有颜色相关的属性可以应用于 :visited:
| 允许的属性 |
|---|
color |
background-color |
border-color(及其子属性) |
column-rule-color |
outline-color |
text-decoration-color |
text-emphasis-color |
fill 和 stroke 的颜色部分 |
额外限制:即使以上属性,已访问和未访问状态之间的透明度差异也不会被应用,防止通过 alpha 通道或 transparent 关键字来区分两种状态。
1
2
3
4
5
6
7
8
9
10
11
12
:link {
outline: 1px dotted blue;
background-color: white;
/* 未访问状态需指定 background-color,
否则默认 transparent 会导致 :visited 的背景色变化不生效 */
}
:visited {
outline-color: orange; /* 允许 */
background-color: green; /* 允许 */
color: yellow; /* 允许 */
}
不能通过
background-image为已访问链接设置不同图片,只有颜色属性可用。
Impact on web developers
- 不能基于
:visited状态切换background-image,只有颜色属性可用 - 透明色(
transparent/alpha通道)在:visited中不会生效,无法通过透明度差异区分已访问和未访问状态
Using :target
:target 伪类用于选中 URL 片段标识符(hash)指向的元素。例如 URL https://example.com/page#reference 中的 #reference 会指向文档中 id="reference" 的元素,此时该元素就是 :target。因为 id 在文档里唯一,所以hash也只有一种命中可能。
Picking a Target(选中目标元素)
选中被 URL hash 指向的特定类型的元素:
1
2
3
4
5
6
7
8
9
10
11
/* 通配规则,不管哪个 h2,只要它刚好是被 hash 指到的那个元素就生效 */
/* 当前 hash 指向的那个 h2 加外框 */
h2:target {
outline: 2px solid;
}
/* 定点规则,只有 hash 是 #reference 时,这个片段加背景色 */
#reference:target {
background-color: yellow;
}
/* 两条规则可以叠加合并生效 */
Targeting all elements(选中所有目标元素)
用 universal selector 对所有被 hash 指向的元素统一创建样式:
1
2
3
:target {
color: red;
}
Example
1
2
3
4
5
6
7
8
9
10
<h4 id="one">…</h4>
<p id="two">…</p>
<div id="three">…</div>
<a id="four">…</a> <em id="five">…</em>
<a href="#one">First</a>
<a href="#two">Second</a>
<a href="#three">Third</a>
<a href="#four">Fourth</a>
<a href="#five">Fifth</a>
点击 “First” 链接 → URL 变为 #one → <h4 id="one"> 成为 :target,浏览器同时会滚动到该元素位置。
Conclusion
当 URL 的 hash 指向文档某一部分时,读者可能困惑”我该看哪里”。通过 :target 为目标元素添加视觉高亮,可以消除这种困惑。
实际开发中 :target 常用于:锚点跳转高亮、纯 CSS 模态框/抽屉(通过 hash 切换显示隐藏)、SPA 中不需要 JS 的页面内导航提示。