【现代 CSS】更强大的 :nth-child 选择器
今天群里讨论了一个非常有意思与 CSS 选择器相关的题目。题目如下:
假设我们如下的 HTML 结构:
<div class="box">
<p class="aa">aaaaaaaaaaaa</p>
<p class="bb">bbbbbbbbbbbb</p>
<p class="cc">cccccccccccc</p>
<p class="aa">aaaaaaaaaaaa</p>
<p class="bb">bbbbbbbbbbbb</p>
<p class="cc">cccccccccccc</p>
<p class="aa">aaaaaaaaaaaa</p>
<p class="bb">bbbbbbbbbbbb</p>
<p class="cc">cccccccccccc</p>
</div>
效果如下:
如何在不添加类名的情况下,快速的选取第一个 class 为 .cc
的元素?
当然,上面的结构示意图只是一种可能的情况,这里想表述的场景的意思是:
- 我们希望选取的
.cc
元素,在其父元素下,存在多个.cc
,所以需要精准定位第一个; .cc
元素一定不是其所有兄弟元素中的第一个;- 所以子元素的标签类型是一样的,都是
<p>
元素或者某个同类标签 - 不允许使用类似
.bb + .cc
的方式直接进行选取,因为第一个.cc
元素的上一个元素不一定是.bb
,可以是其他任何元素;
基于上述的限定条件,你可以暂停阅读,思考 20 秒,可能的方式有哪些?
使用 :nth-child 或者 :nth-of-type
一般而言,比较容易想到的,肯定是首先使用 :nth-child
和 :nth-of-type
伪类选择器进行尝试。
但是,遗憾的是,这两个选择器都无法做到在上述结构下,成功选取第一个 .cc
元素。
代码如下:
.box .cc:nth-child(1) {
color: red;
font-size: 24px;
}
或者
.box .cc:nth-of-type(1) {
color: red;.box .cc:nth-child(1) {
color: red;
font-size: 24px;
}
也就是说,上述两种方式,都是不行的。简单解释一下:
- 对于
:nth-child
而言,:nth-child()
伪类根据元素在父元素的子元素列表中的索引来选择元素。最为核心的是,选择器,它是根据父元素内的所有兄弟元素的位置来选择子元素。
这里最重要的是,它是根据父元素内的所有兄弟元素的位置来选择子元素,而我们无法得知在实际业务场景下,第一个 .cc
到底处于第几个索引。因此,这个选择器明显没法胜任。
- 对于
:nth-of-type
而言,基于相同类型(标签名称)的兄弟元素中的位置来匹配元素。
非常重要的一点是,使用此选择器无法选择基于相同类名的元素的位置,来匹配元素。
什么意思呢?
如果我们把上述 DEMO 改造,改造成这样:
<div class="box">
<p class="aa">aaaaaaaaaaaa</p>
<span class="bb">bbbbbbbbbbbb</span>
<div class="cc">cccccccccccc</div>
<p class="aa">aaaaaaaaaaaa</p>
<span class="bb">bbbbbbbbbbbb</span>
<div class="cc">cccccccccccc</div>
<p class="aa">aaaaaaaaaaaa</p>
<span class="bb">bbbbbbbbbbbb</span>
<div class="cc">cccccccccccc</div>
</div>
再基于上述结构下,选择第一个 class 为 .cc
的元素,就可以利用 :nth-of-type
实现。因为,所有的 .cc
都是 div 元素,且没有其它 div
元素:
.box div:nth-of-type(1) {
color: red;
font-size: 24px;
}
效果如下:
当然,实际情况远没有如此乐观。在所有子元素标签情况无法确定的情况下,基于上述讨论,使用 :nth-child
或者 :nth-of-type
都不可行。还有什么办法呢?
使用 :nth-of-class 伪类?
按照上面说的,如果存在一个伪类 :nth-of-class
,可以实现,基于相同类名的元素的位置,来匹配元素,那么问题就解决了。
但是实际情况是,目前 CSS 规范仍未支持 :nth-of-class
伪类选择器。
我们必须另辟蹊径。
巧用 :not() 与后代选择器 ~
好,现在我们换个思路,如果要我们选择,非第一个 class 为 .cc
的 .cc
元素,可以怎么实现呢?
可以利用 ~
后续兄弟选择器实现:
.box .cc ~ .cc {
color: red;
font-size: 24px;
}
效果如下:
成功了!此时,我们只需要基于上述结果,取反即可。在现代 CSS 中,我们可以利用 :not()
伪类实现。
:not()
伪类::not()
CSS 伪类用来匹配不符合一组选择器的元素。由于它的作用是防止特定的元素被选中,它也被称为反选伪类(negation pseudo-class)。
改造一下上述的代码:
.box .cc:not(.cc ~ .cc) {
color: red;
font-size: 24px;
}
这样,我们就成功的选取到了第一个 .cc
元素。
还有办法吗?
是的,在现代 CSS 中,:nth-child()
提供了一种更为强大的方式供我们使用。
使用 :nth-child 伪类的现代特性
从 Chrome 111,:nth-child()
提供了一种更为强大的特性,让我们能够方便的实现上述的效果,这种语法就是 :nth-child(1 of .foo)
。
看看 [CanIUse - :nth-child(1 of .foo)(caniuse.com/?search=%3A…
我们能够通过 :nth-child(1 of .foo)
这个特性,间接实现 :nth-of-class
的效果:
.box .cc:nth-child(1 of .cc) {
color: red;
font-size: 24px;
}
效果如下:
当然,利用这个特性,可以快速的选择任意 index 的 .cc
元素:
譬如第二个:
.box .cc:nth-child(2 of .cc) {
color: red;
font-size: 24px;
}
效果如下:
完整的 DEMO,你可以戳这里:CodePen Demo -- 选择子元素下第一个类名为 xx 的元素
此功能由规范 Selectors Level 4 - :nth-child() pseudo-class 提出,感兴趣的可以看看规范原文定义。
一个非常有意思的现代选择器功能,你学会了吗?
最后
好了,本文到此结束,希望本文对你有所帮助 :)
想 Get 到最有意思的 CSS 资讯,千万不要错过我的公众号 -- iCSS前端趣闻 😄
更多精彩 CSS 技术文章汇总在我的 Github -- iCSS ,持续更新,欢迎点个 star 订阅收藏。
如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。
转载自:https://juejin.cn/post/7385929329640177676