Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

【现代 CSS】更强大的 :nth-child 选择器 #267

Open
chokcoco opened this issue Jun 29, 2024 · 1 comment
Open

【现代 CSS】更强大的 :nth-child 选择器 #267

chokcoco opened this issue Jun 29, 2024 · 1 comment

Comments

@chokcoco
Copy link
Owner

chokcoco commented Jun 29, 2024

今天群里讨论了一个非常有意思与 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 的元素

当然,上面的结构示意图只是一种可能的情况,这里想表述的场景的意思是:

  1. 我们希望选取的 .cc 元素,在其父元素下,存在多个 .cc所以需要精准定位第一个
  2. .cc 元素一定不是其所有兄弟元素中的第一个
  3. 所以子元素的标签类型是一样的,都是 <p> 元素或者某个同类标签
  4. 不允许使用类似 .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;
}

也就是说,上述两种方式,都是不行的。简单解释一下:

  1. 对于 :nth-child 而言,:nth-child() 伪类根据元素在父元素的子元素列表中的索引来选择元素。最为核心的是,选择器,它是根据父元素内的所有兄弟元素的位置来选择子元素

这里最重要的是,它是根据父元素内的所有兄弟元素的位置来选择子元素,而我们无法得知在实际业务场景下,第一个 .cc 到底处于第几个索引。因此,这个选择器明显没法胜任。

  1. 对于 :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 伪类选择器。

我们必须另辟蹊径。

巧用 :has() 与后代选择器 ~

好,现在我们换个思路,如果要我们选择,非第一个 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)(https://caniuse.com/?search=%3Anth-child):

我们能够通过 :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 提出,感兴趣的可以看看规范原文定义。

进阶理解新特性 of S 规则及实际应用演示

在掌握 of S 这个规则之后,我们就可以轻松的 Cover 非常多在之前选取起来非常困难的一些场景。

我们举几个例子一起看看,假设我们有如下的结构:

<div class="g-container"> 
    <div class="g-item-triangle"></div>
    <div class="g-item-star"></div>
    <div class="g-item-hexagon"></div>
    <div class="g-item-triangle"></div>
    <div class="g-item-star"></div>
    <div class="g-item-hexagon"></div>
    <div class="g-item-star"></div>
    <div class="g-item-hexagon"></div>
    <div class="g-item-triangle"></div>
    <div class="g-item-star"></div>
    <div class="g-item-hexagon"></div>
    <div class="g-item-triangle"></div>
    <div class="g-item-triangle"></div>
    <div class="g-item-star"></div>
    <div class="g-item-hexagon"></div>
    <div class="g-item-triangle"></div>
    <div class="g-item-star"></div>
    <div class="g-item-hexagon"></div>
</div>

简单的 CSS 代码如下:

.g-container {
    position: relative;
    width: 800px;
    height: 300px;
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
    
    & > div {
        width: 120px;
        height: 120px;
        flex-shrink: 0;
        background: #fc0;
    }
    
    .g-item-triangle {
        clip-path: polygon(50% 0, 100% 100%, 0 100%);
    }
    
    .g-item-star {
        clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
    }
    .g-item-hexagon {
        clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);

    }
}

即可得到如下结构:

由于上述的三角形 .g-item-triangle、星形 .g-item-star以及六边形 .g-item-hexagon 都在同一个父容器下,并且它们都是使用 div 表示,只是有不同的 class 进行表示。

1.选取第二个六边形元素

首先,我们来看看,我们的诉求是,如何选取所有元素下的第二个六边形元素?

你可能认为可以使用如下代码?

.g-item-hexagon:nth-child(2) {
    background: #f00;
}

按照我们上面文章提到的,这显然是选不到的。

但是如果你了解了 :nth-childof S 特性,这个问题就非常简单了:

:nth-child(2 of .g-item-hexagon) {
    background: #f00;
}

这样,成功选取到了所有元素下的第二个六边形元素:

2. 选取偶数位数的三角形

继续,此外,of S 规范还支持 2nevenodd 这样匹配规则。

因此,当我们想选取偶数位数的三角形时,可以写成:

:nth-child(2n of .g-item-star) {
    background: #f00;
}
// OR, 下述写法也可以
:nth-child(even of .g-item-star) {
    background: #f00;
}

效果如下:

3. 选取去掉星形元素后的所有奇数个数元素

最后,of S 规则还可以在内部再使用 :not() 伪类,实现某些特定元素的剔除。

譬如,我们希望去掉所有元素内部的星星元素,在剩下的元素中,再选取所有奇数序号的元素。

听起来有点麻烦,写起来很简单:

:nth-child(2n+1 of :not(.g-item-star)) {
    background: #f00;
}

此时,效果如下:

这里应用 :not() 规则后,相当于把 .g-item-star 去掉,剩余的 .g-item-hexagon.g-item-triangle 当成一个新的整体,应用 of S 前的规则。需要好好理解一下。

完整的 DEMO,你可以戳这里:CodePen Demo -- nth-child of S rule Demo

最后

好了,本文到此结束,一个非常有意思的现代选择器功能,你学会了吗?希望本文对你有所帮助 :)

想 Get 到最有意思的 CSS 资讯,千万不要错过我的公众号 -- iCSS前端趣闻 😄

更多精彩 CSS 技术文章汇总在我的 Github -- iCSS ,持续更新,欢迎点个 star 订阅收藏。

如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。

@zhangenming
Copy link

这个会选取每个元素下的第一个
有没有办法选区整个document下的第一个
更进一步,限定到某个范围内的第一个

# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

No branches or pull requests

2 participants