首页 CSSDAY1 Selectors And Cascade: Selectors
文章
取消

CSSDAY1 Selectors And Cascade: Selectors

开始根据林想的Web工坊大佬的建议路线学习CSS。

本文涉及内容会如下:

CSS selectors - CSS | MDN

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)值匹配默认不区分大小写;非枚举属性(如 classiddata-*rolearia-*)默认区分大小写,可加 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 层面完全无法检测链接是否被访问过。

只有颜色相关的属性可以应用于 :visited

允许的属性
color
background-color
border-color(及其子属性)
column-rule-color
outline-color
text-decoration-color
text-emphasis-color
fillstroke 的颜色部分

额外限制:即使以上属性,已访问和未访问状态之间的透明度差异也不会被应用,防止通过 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 的页面内导航提示。

本文由作者按照 CC BY 4.0 进行授权

阅读DAY17 JavaScript高级程序设计 12章上 BOM

阅读DAY18 JavaScript高级程序设计 12章下 BOM