Manchan's blog Manchan's blog
首页
  • 前端文章

    • HTML5
    • CSS3
    • JavaScript
  • 学习笔记

    • CSS选择器世界
地信
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 旧版博客
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
友链
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

まん酱

一个正在学习中的GISer~
首页
  • 前端文章

    • HTML5
    • CSS3
    • JavaScript
  • 学习笔记

    • CSS选择器世界
地信
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 旧版博客
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
友链
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 《CSS选择器世界》

    • 概述
    • CSS选择器的优先级
    • CSS选择器的命名
    • 精通CSS选择符
      • 后代选择符空格()
      • 子选择符箭头(>)
      • 相邻兄弟选择符加号(+)
      • 随后兄弟选择符弯弯(~)
    • 元素选择器
    • 属性选择器
    • 用户行为伪类
    • URL定位伪类
    • 输入伪类
    • 树结构伪类
  • 学习笔记
  • 《CSS选择器世界》
GIS君-manchan
2022-11-24
目录

精通CSS选择符

CSS 选择符目前有下面这几个:后代选择符空格()子选择符头(>)、相兄弟选择符加号(+)、随后兄弟选择符弯弯 (~)。

# 后代选择符空格()

# 对CSS后代选择符可能错误的认识

<html>
<div demo4-1>
  <div class="lightblue">
    <div class="darkblue">
      <p>1. 颜色是?</p>
    </div>
  </div>
  <div class="darkblue">
    <div class="lightblue">
      <p>2. 颜色是?</p>
    </div>
  </div>
</div>

</html>

<style>
  [demo4-1] .lightblue {
    color: lightblue;
  }

  [demo4-1] .darkblue {
    color: darkblue;
  }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
  "horizontal": true
}
1
2
3

上面这个例子里因为 color 具有继承特性,所以文字的颜色由 DOM 最深的赋色元素决定,因此1和2的颜色分别是深蓝色和浅蓝色。

<html>
<div demo4-2>
  <div class="lightblue">
    <div class="darkblue">
      <p>1. 颜色是?</p>
    </div>
  </div>
  <div class="darkblue">
    <div class="lightblue">
      <p>2. 颜色是?</p>
    </div>
  </div>
</div>

</html>

<style>
  [demo4-2] .lightblue p {
    color: lightblue;
  }

  [demo4-2] .darkblue p {
    color: darkblue;
  }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
  "horizontal": true
}
1
2
3

上面这个例子很容易出错,原因就在于他们对后代选择符有错误的认识,当包含后代选择符的时候整个选择器的优先级与祖先元素的 DOM 层级没有任何关系,这时要看落地元素的优先级。

在本例中,落地元素就是最后的 <p>元素。两个 <p>元素彼此分离,非嵌套,因此 DOM 层级平行没有先后;再看选择器的优先级,.lightblue p和 darkblue p 是一个类选择器 (数值10)和一个标签选择器(数值 1),选择器优先级的计算值一样;此时就要看它们在 CSS 文件中的位置,遵循“后来居上”的规则,由于 .darkblue p 更靠后,因此,<p>都是按照 color:darkblue 进行颜色渲染的,于是,最终1和2的文字颜色全部都是深蓝色。

# 对JavaScript中后代选择符可能错误的认识

<html>
<div demo4-3>
  <div class="lonely">
    <code>document.querySelectorAll('[demo4-3] div div').length</code><br>语句的返回长度是<span></span>
  </div>
  <div class="outer">
    <div class="inner">
      <code>document.querySelector('[demo4-3]').querySelectorAll('div div').length</code><br>语句的返回长度是<span></span><br>
      <code>document.querySelector('[demo4-3]').querySelectorAll(':scope div div').length</code><br>语句的返回长度是<span></span>
    </div>
  </div>
</div>

</html>

<script>
  let span = document.querySelectorAll('[demo4-3] span')

  span[0].innerHTML = document
    .querySelectorAll('[demo4-3] div div')
    .length
  span[1].innerHTML = document
    .querySelector('[demo4-3]')
    .querySelectorAll('div div')
    .length
  span[2].innerHTML = document
    .querySelector('[demo4-3]')
    .querySelectorAll(':scope div div')
    .length
</script>
<style>
  [demo4-3] span {
    font-weight: bold
  }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

为什么第二条语句的NodeList的长度是3呢?

因为CSS选择器是独立于整个页面的。querySelectorAll里面的选择器同样也是全局特性,所以 document.querySelector('[demo4-3]').querySelectorAll('div div').length翻译过来的意思是:查询 [demo4-3]元素的子元素选择所有同时满足整个页面下 div div 选择器条件的DOM元素。

要想 querySelectorAll 后面的选择器不是全局匹配可以使用 :scope伪类,其作用就是让 CSS 选择器的作用域局限在某一范围内。

# 子选择符箭头(>)

# 子选择符和后代选择符的区别

子选择符只会匹配第一代子元素,而后代选择符会匹配所有子元素。

<html>
<ol demo4-4>
  <li>颜色是?</li>
  <li>颜色是?
    <ul>
      <li>颜色是?</li>
      <li>颜色是?</li>
    </ul>
  </li>
  <li>颜色是?</li>
</ol>

</html>

<style>
  ol[demo4-4] li {
    color: darkblue;
    text-decoration: underline;
  }

  ol[demo4-4]>li {
    color: lightblue;
    -webkit-text-decoration: underline wavy;
    text-decoration: underline wavy;
  }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
  "horizontal": true
}
1
2
3

可以看到,外层所有文字的下划线都只有波浪类型,而内层文字的下划线是实线和波浪线的混合类型。而实线下划线是 ol li 选择器中的 text-decoration:underline 声明产生的,波浪线下划线是 ol>li 选择器中的 text-decoration:underline wavy声明产生的,这就说明,o1>li 只能作用于当前子 <li>元素,而 ol li 可以作用于所有的后代 <li>元素。

以上就是这两个选择符的差异。显然后代选择符的匹配范围要比子选择符的匹配范围更广,因此,同样的选择器下,子选择符的匹配性能要优于后代选择符。但这种性能优势的价值有限,几乎没有任何意义,因此不能作为选择符技术选型的优先条件。

适合使用子选择符的场景通常有以下几个。

(1) 状态类名控制。例如使用 .active 类名进行状态切换,会遇到祖先和后代都存在 .active 切换的场景,此时子选择符是必需的,以免影响后代元素。

(2) 标签受限。例如当 <li>标签重复嵌套,同时我们无法修改标签名称或者设置类名的时候,就需要使用子选择符进行精确控制。

(3)层级位置与动态判断。例如一个时间选择组件的 HTML 通常会放在 <body>元素下,作为 <body>的子元素,以绝对定位浮层的形式呈现。但有时候其需要以静态布局嵌在页面的某个位置,这时如果我们不方便修改组件源码,则可以借助子选择符快速打一个补丁 意思就是当组件容器不是 <body>子元素的时候取消绝对定位。子选择符就是把双刃剑,它通过限制关系使得结构更加稳固,但同时也失去了弹性和变化,需要审慎使用。

# 相邻兄弟选择符加号(+)

相邻兄弟选择符也是非常实用的选择符,IE7 及以上版本的浏览器支持,它可以用于选择相邻的兄弟元素,但只能选择后面一个兄弟。

# 相邻兄弟选择符的相关细节

  1. 文本节点与相邻兄弟选择符

相邻兄弟选择符会忽略文本节点。

  1. 注释节点与相邻兄弟选择符

相邻兄弟选择符会忽略注释节点。

# 实现类似 :first-child的效果

<html>
<div demo4-5>
  <div class="cs-g1">
    <h4>使用:first-child实现</h4>
    <p class="cs-li">列表内容1</p>
    <p class="cs-li">列表内容2</p>
    <p class="cs-li">列表内容3</p>
  </div>
  <div class="cs-g2">
    <h4>使用相邻兄弟选择符实现</h4>
    <p class="cs-li">列表内容1</p>
    <p class="cs-li">列表内容2</p>
    <p class="cs-li">列表内容3</p>
  </div>
</div>

</html>

<style>
  [demo4-5] .cs-g1 .cs-li:not(:first-child) {
    color: skyblue;
  }

  [demo4-5] .cs-g2 .cs-li+.cs-li {
    color: skyblue;
  }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
  "horizontal": true
}
1
2
3

# 众多高级选择器技术的核心

相邻兄弟选择符最硬核的应用还是配合诸多伪类低成本实现很多实用的交互效果,是众多 高级选择器技术的核心。

<html>
<div demo4-6>
  用户名:<input><span class="cs-tips">不超过10个字符</span>
</div>

</html>

<style>
  [demo4-6] .cs-tips {
    color: gray;
    margin-left: 15px;
    position: absolute;
    visibility: hidden;
  }

  [demo4-6] :focus+.cs-tips {
    visibility: visible;
  }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 随后兄弟选择符弯弯(~)

# 和相邻兄弟选择符区别

相邻兄弟选择符只会匹配它后面的第一个兄弟元素,而随后兄弟选择符会匹配后面的所有兄弟元素。

# 为什么没有前面兄弟选择符

我们可以看到,无论是相邻兄弟选择符还是随后兄弟选择符,它们都只能选择后面的元素,我第一次认识这两个选择符的时候,就有这么一个疑问:为什么没有前面兄弟选择符?后来我才明白,没有前面兄弟选择符和没有父元素选择符的原因是一样的,它们都受制于DOM 渲染规则。

浏览器解析 HTML 文档是从前往后,由外及里进行的,所以我们时常会看到页面先出现头部然后再出现主体内容的情况。

但是,如果 CSS 支持了前面兄弟选择符或者父元素选择符,那就必须要等页面所有子元素加载完毕才能染 HTML 文档。因为所谓“前面兄弟选择符”,就是后面的 DOM 元素影响前面的 DOM 元素,如果后面的元素还没被加载并处理,又如何影响前面的元素样式呢?如果 CSS真的支持这样的选择符,网页呈现速度必然会大大减慢,浏览器会出现长时间的白板,这会造成不好的体验。

有人可能会说,依然强制采取加载到哪里就渲染到哪里的策略呢?这样做会导致更大的问题,因为会出现加载到后面的元素的时候,前面的元素已经渲染好的样式会突然变成另外一个样式的情况,这也会造成不好的体验,而且会触发强烈的重排和重绘。

实际上,现在规范文档有一个伪类:has 可以实现类似父选择器和前面选择器的效果,且这个伪类 2013 年就被提出过,但是这么多年过去了,依然没有任何浏览器实现相关功能。在我看来,就算再过 5到 10年,CSS 支持“前面兄弟选择符”或者“父选择符”的可能性也很低,这倒不是技术层面上实现的可能性较低,而是 CSS 和 HTML 本身的染机制决定了这样的结果。

# 来了,来了,CSS :has()伪类她来了

根据MDN以及Can I Use网站显示,:has()伪类已经开始被浏览器实现!

image

image

主流浏览器的最新版本均已经支持了 :has()伪类,Firefox亦可通过相关设置开启。

我们用几个例子来看一下这个“新伪类”,以下例子均需要在上图所示的受支持的浏览器才能完整展示。

<html>
<div demo4-7>
 <section><div>这是section的第一个div</div><div>这是section的第二个div</div></section>
 <section><nav><div>这是section后nav的div</div></nav></section>
 <div>这是dt前的div</div>
 <dt>这是一个dt</dt>
</div>
<div demo4-8>
 <section><h5>这是只有h5的section</h5></section>
 <section><p>这是没有h5的section</p></section>
 <section><h5>不仅有h5</h5><p>还有p</p></section>
</div>

</html>

<style>
  /* 匹配后代有div的section */
  [demo4-7] section:has(div){
    color: #dd4a68;
  }
  /* 匹配子代是dic的section,更后的不匹配 */
  [demo4-7] section:has(> div){
    color: #0085ad;
  }
  /* 匹配相邻兄弟是dt的div 即匹配dt前的div */
  [demo4-7] div:has(+ dt){
    color: #f08d49;
  }
  /* 匹配后代没有h5的section */
  [demo4-8] section:not(:has(h5)){
    color: #fff;
  }
  /* 匹配后代有不是h5的后代 */
  [demo4-8] section:has(:not(h5)){
    background-color: #7ead64;
  }
  /* 上面两个有交集,优先级相同,会层叠,故演示为不同的属性 */
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
微信 支付宝
#CSS#CSS选择器
文章更新于: 2022/11/25 19:40:50
CSS选择器的命名
元素选择器

← CSS选择器的命名 元素选择器→

最近更新
01
文档
02-03
02
前端面试学习
01-02
03
Markdown首行缩进
12-01
更多文章>
Theme by Vdoing | Copyright © 2020-2023 Manchan All rights reserved.
鄂ICP备2020015911-1号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式