大家好,我是 Coco。
今天我们来讨论了一个非常有意思与 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()
伪类[1]根据元素在父元素的子元素列表中的索引来选择元素。最为核心的是,选择器,它是根据父元素内的所有兄弟元素的位置来选择子元素。
这里最重要的是,它是根据父元素内的所有兄弟元素的位置来选择子元素,而我们无法得知在实际业务场景下,第一个 .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[2] 伪类[3]用来匹配不符合一组选择器的元素。由于它的作用是防止特定的元素被选中,它也被称为反选伪类(negation pseudo-class)。
改造一下上述的代码:
.box .cc:not(.cc ~ .cc) {
color: red;
font-size: 24px;
}
这样,我们就成功的选取到了第一个 .cc
元素。
还有办法吗?
是的,在现代 CSS 中,:nth-child()
提供了一种更为强大的方式供我们使用。
使用 :nth-child 伪类的现代特性 of S 规则
从 Chrome 111,:nth-child()
提供了一种更为强大的特性,让我们能够方便的实现上述的效果,这种语法就是 :nth-child(1 of .foo)
。
其中,上述代码中的 1 of .foo
就是所谓的 of S
规则。
看看 Can I Use - :nth-child(1 of .foo)[4]
我们能够通过 :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 的元素[5]
此功能由规范 Selectors Level 4 - :nth-child() pseudo-class[6] 提出,感兴趣的可以看看规范原文定义。
进阶理解新特性 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-child
的 of S
特性,这个问题就非常简单了:
:nth-child(2 of .g-item-hexagon) {
background: #f00;
}
这样,成功选取到了所有元素下的第二个六边形元素:
2. 选取偶数位数的三角形
继续,此外,of S
规范还支持 2n
、even
、odd
这样匹配规则。
因此,当我们想选取偶数位数的三角形时,可以写成:
: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[7]
最后
好了,本文到此结束,一个非常有意思的现代选择器功能,你学会了吗?希望本文对你有所帮助 :)
更多精彩 CSS 技术文章汇总在我的 Github -- iCSS[8] ,持续更新,欢迎点个 star 订阅收藏。
如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。
伪类: https://developer.mozilla.org/zh-CN/docs/Web/CSS/Pseudo-classes
[2]CSS: https://developer.mozilla.org/zh-CN/docs/Web/CSS
[3]伪类: https://developer.mozilla.org/zh-CN/docs/Web/CSS/Pseudo-classes
[4]Can I Use - :nth-child(1 of .foo): https://caniuse.com/?search=%3Anth-child
[5]CodePen Demo -- 选择子元素下第一个类名为 xx 的元素: https://codepen.io/Chokcoco/pen/mdYYdeY
[6]Selectors Level 4 - :nth-child() pseudo-class: https://www.w3.org/TR/selectors-4/#nth-child-pseudo
[7]CodePen Demo -- nth-child of S rule Demo: https://codepen.io/Chokcoco/pen/jOjLVYW
[8]Github -- iCSS: https://github.com/chokcoco/iCSS
点赞
和在看
是最大的支持 ⬇️❤️⬇️