精通CSS选择符
CSS 选择符目前有下面这几个:后代选择符空格()子选择符头(
>
)、相兄弟选择符加号(+
)、随后兄弟选择符弯弯 (~
)。
# 后代选择符空格()
# 对 CSS 后代选择符可能错误的认识
上面这个例子里因为 color 具有继承特性,所以文字的颜色由 DOM 最深的赋色元素决定,因此 1 和 2 的颜色分别是深蓝色和浅蓝色。
上面这个例子很容易出错,原因就在于他们对后代选择符有错误的认识,当包含后代选择符的时候整个选择器的优先级与祖先元素的 DOM 层级没有任何关系,这时要看落地元素的优先级。
在本例中,落地元素就是最后的 <p>
元素。两个 <p>
元素彼此分离,非嵌套,因此 DOM 层级平行没有先后;再看选择器的优先级,.lightblue p
和 darkblue p
是一个类选择器 (数值 10)和一个标签选择器(数值 1),选择器优先级的计算值一样;此时就要看它们在 CSS 文件中的位置,遵循“后来居上”的规则,由于 .darkblue p
更靠后,因此,<p>
都是按照 color:darkblue
进行颜色渲染的,于是,最终 1 和 2 的文字颜色全部都是深蓝色。
# 对 JavaScript 中后代选择符可能错误的认识
为什么第二条语句的 NodeList 的长度是 3 呢?
因为 CSS 选择器是独立于整个页面的。querySelectorAll
里面的选择器同样也是全局特性,所以 document.querySelector('[demo4-3]').querySelectorAll('div div').length
翻译过来的意思是:查询 [demo4-3]
元素的子元素选择所有同时满足整个页面下 div div
选择器条件的 DOM 元素。
要想 querySelectorAll 后面的选择器不是全局匹配可以使用 :scope
伪类,其作用就是让 CSS 选择器的作用域局限在某一范围内。
# 子选择符箭头(>
)
# 子选择符和后代选择符的区别
子选择符只会匹配第一代子元素,而后代选择符会匹配所有子元素。
可以看到,外层所有文字的下划线都只有波浪类型,而内层文字的下划线是实线和波浪线的混合类型。而实线下划线是 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 及以上版本的浏览器支持,它可以用于选择相邻的兄弟元素,但只能选择后面一个兄弟。
# 相邻兄弟选择符的相关细节
- 文本节点与相邻兄弟选择符
相邻兄弟选择符会忽略文本节点。
- 注释节点与相邻兄弟选择符
相邻兄弟选择符会忽略注释节点。
# 实现类似 :first-child
的效果
# 众多高级选择器技术的核心
相邻兄弟选择符最硬核的应用还是配合诸多伪类低成本实现很多实用的交互效果,是众多 高级选择器技术的核心。
# 随后兄弟选择符弯弯(~
)
# 和相邻兄弟选择符区别
相邻兄弟选择符只会匹配它后面的第一个兄弟元素,而随后兄弟选择符会匹配后面的所有兄弟元素。
# 为什么没有前面兄弟选择符
我们可以看到,无论是相邻兄弟选择符还是随后兄弟选择符,它们都只能选择后面的元素,我第一次认识这两个选择符的时候,就有这么一个疑问:为什么没有前面兄弟选择符?后来我才明白,没有前面兄弟选择符和没有父元素选择符的原因是一样的,它们都受制于 DOM 渲染规则。
浏览器解析 HTML 文档是从前往后,由外及里进行的,所以我们时常会看到页面先出现头部然后再出现主体内容的情况。
但是,如果 CSS 支持了前面兄弟选择符或者父元素选择符,那就必须要等页面所有子元素加载完毕才能染 HTML 文档。因为所谓“前面兄弟选择符”,就是后面的 DOM 元素影响前面的 DOM 元素,如果后面的元素还没被加载并处理,又如何影响前面的元素样式呢?如果 CSS 真的支持这样的选择符,网页呈现速度必然会大大减慢,浏览器会出现长时间的白板,这会造成不好的体验。
有人可能会说,依然强制采取加载到哪里就渲染到哪里的策略呢?这样做会导致更大的问题,因为会出现加载到后面的元素的时候,前面的元素已经渲染好的样式会突然变成另外一个样式的情况,这也会造成不好的体验,而且会触发强烈的重排和重绘。
实际上,现在规范文档有一个伪类:has 可以实现类似父选择器和前面选择器的效果,且这个伪类 2013 年就被提出过,但是这么多年过去了,依然没有任何浏览器实现相关功能。在我看来,就算再过 5 到 10 年,CSS 支持“前面兄弟选择符”或者“父选择符”的可能性也很低,这倒不是技术层面上实现的可能性较低,而是 CSS 和 HTML 本身的染机制决定了这样的结果。
# 来了,来了,CSS :has()伪类她来了
根据 MDN 以及 Can I Use 网站显示,:has()
伪类已经开始被浏览器实现!
主流浏览器的最新版本均已经支持了 :has()
伪类,Firefox 亦可通过相关设置开启。
我们用几个例子来看一下这个“新伪类”,以下例子均需要在上图所示的受支持的浏览器才能完整展示。