Skip to content
New issue

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

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

Already on GitHub? Sign in 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下的第一个
更进一步,限定到某个范围内的第一个

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

No branches or pull requests

2 participants