首页 > 编程笔记

CSS :nth-child()和:nth-last-child()伪类详解

:nth-last-child() 伪类和 :nth-child() 伪类的区别在于,:nth-last-child() 伪类是从后面开始按指定序号匹配,而 :nth-child() 伪类是从前面开始匹配。除此之外,无论是在兼容性还是语法方面,两者都没有区别。

因此,本节会以 :nth-child() 为代表对这两个伪类进行详细且深入的介绍。

从:nth-child()开始说起

在介绍语法之前,有必要提一句,:nth-child() 伪类虽然功能很强大,但只适用于内容动态、无法确定的匹配场景。如果数据是纯静态的,哪怕是列表,都要使用类名或者属性选择器进行匹配。

例如:
<ol>
   <li class="cs-li cs-li-1">内容</li>
   <li class="cs-li cs-li-2">内容</li>
   <li class="cs-li cs-li-3">内容</li>
</ol>
没有必要使用 li:nth-child(1)、li:nth-child(2) 和 li:nth-child(3),因为这样会增加选择器的优先级,且由于 DOM 结构严格匹配,无法随意调整,不利于维护。

:nth-child() 伪类可以匹配指定索引序号的元素,支持一个参数,且必须有参数,参数可以是关键字值或者函数符号这两种类型。

1) 关键字值的形式如下。

可以这样记忆:如果字母个数是奇数(odd 有 3 个字母),就匹配奇数个数的元素;如果字母个数是偶数(even 有 4 个字母),就匹配偶数个数的元素。

奇偶匹配关键字多用在列表或者表格中,可以用来实现提升阅读体验的斑马线效果。

2) 函数符号的形式如下:

An+B

其中 A 和 B 都是固定的数值,且必须是整数;n 可以理解为从 0 开始的自然数序列(0, 1, 2, 3, …),n 前面可以有负号。第一个子元素的匹配序号是 1,小于 1 的计算序号会被忽略。

下面来看一些示例,快速了解一下各种类型的参数的含义:

实际示例

:nth-child() 适合用在列表元素数量不可控的场景下,如表格、列表等。下面举 3 个常用示例。

1) 列表斑马线条纹

此效果多用在密集型大数量的列表或者表格中,不容易看串行,通常设置列表的偶数行为深色背景,代码示意如下:
table {
   border-spacing: 0;
   width: 300px;
   text-align: center;
   border: 1px solid #ccc;
}
tr {
   background-color: #fff;
}
tr:nth-child(even) {
   background-color: #eee;
}
布局效果如下图所示。


图 1 列表斑马线条纹效果

2) 列表边缘对齐

例如,要实现下图所示的布局效果。


图 2 列表边缘对齐效果

如果无须兼容 IE 浏览器,最好的实现方法是 display:grid 布局。如果需要兼容一些老旧的浏览器,多半会使用浮动或者 inline-block 排列布局,此时间距的处理是难点,因为无论是设置 margin-left 还是 margin-right,都无法实现正好两端紧贴边缘。

使用 :nth-child() 伪类是比较容易理解和上手的一种方法,假设间距固定为 10px,则 CSS 代码示意如下:
li {
   float: left;
   width: calc((100% - 40px) / 5);
   margin-right: 10px;
}
li:nth-child(5n) {
   margin-right: 0;
}
或者下面更推荐使用的写法:
li {
   float: left;
   width: calc((100% - 40px) / 5);
}
li:not(:nth-child(5n)) {
   margin-right: 10px;
}

3) 固定区间的列表高亮

前面提到过这个应用,例如,在展示考试成绩的列表中,前 10 名需要高亮显示,前 3 名着重高亮,要实现这样的效果,没有比使用 :nth-child() 伪类更合适的方法了。

CSS 代码如下:
/* 前3行背景色为素色 */
tr:nth-child(-n + 3) td {
   background: bisque;
}
/* 4-10行背景色为淡青色 */
tr:nth-child(n + 4):nth-child(-n + 10) td {
   background: lightcyan;
}
效果如下图所示:


图 3 指定列表范围的背景色效果

动态列表项数量匹配技术

聊天软件中的群头像或者一些书籍的分组往往采用复合头像作为一个大的头像,如下图所示,可以看到如果头像数量不同,布局就会不同。


图 4 头像数量不同,布局不同

通常大家会使用下面的方法进行布局:
<ul class="cs-box" data-number="1"></ul>
<ul class="cs-box" data-number="2"></ul>
<ul class="cs-box" data-number="3"></ul>
...
.cs-box[data-number="1"] li {}
.cs-box[data-number="2"] li {}
.cs-box[data-number="3"] li {}
这个实现方法可以很好地满足我们的开发需求,不足是当子头像的数量变化时,需要同时修改 data-number 的属性值,有一定的维护成本。

实际上,还有更巧妙的实现方法,那就是借助子索引伪类自动判断列表项的个数,从而实现我们想要的布局。在这个方法中,不需要在父元素上设置当前列表项的个数,因此,HTML 看起来平淡无奇:
<ul class="box">
  <li></li>
  <li></li>
  <li></li>
  ...
</ul>
关键在于 CSS,我们可以借助伪类判断当前列表项的个数,示意代码如下:
/* 1个 */
li:only-child {}
/* 2个 */
li:first-child:nth-last-child(2) {}
/* 3个 */
li:first-child:nth-last-child(3) {}
...
其中,:first-child:nth-last-child(2) 表示当前 <li> 元素既匹配第一个子元素,又匹配从后往前的第二个子元素,因此,我们就能判断当前总共有两个 <li> 子元素,从而精准实现我们想要的布局了,只需要配合使用相邻兄弟选择符加号(+)以及兄弟选择符(~)。

例如:
/* li列表项的数量大于或等于5时所有元素宽度为33.333% */
li:first-child:nth-last-child(n+5),
li:first-child:nth-last-child(n+5) ~ li {
  width: calc(100% / 3);
}
以上是不是一个非常巧妙的实现呢?

总之,基于上面的数量匹配原理就能自动实现不同列表项数量下的不同布局效果,而无须设置专门表示列表项数量的类名或者属性。

实现效果如下图所示:


图 5 不同头像数量下不同布局的实现效果

其中,HTML 结构如下:
<div class="cs-box">
   <!-- 1-9个li元素  -->
   <cs-li></cs-li>
</div>
CSS 部分和布局相关的代码如下:
.cs-box {
  display: inline-flex;
  align-content: center;
  flex-wrap: wrap-reverse;
  justify-content: center;
  width: 140px; height: 140px;
}
cs-li {
  flex: none;
  width: 50%;
  aspect-ratio: 1;
}
/*只有1个li元素*/
cs-li:only-child {
  width: 100%;
}
/*不少于5个li元素*/
cs-li:first-child:nth-last-child(n+5),
cs-li:first-child:nth-last-child(n+5) ~ cs-li {
  width: calc(100% / 3);
}

nth-child()的参数索引特性

对于 nth-child() 和 nth-last-child(),还有一种大家可能没见过的语法,就是使用 of 关键字配合特性的 CSS 选择器,对已经匹配的树结构元素进行进一步的匹配。

例如,有如下 HTML 结构和 CSS 代码:
<dl>
  <dt>标题</dt>
  <dd>列表1</dd>
  <dd>列表2</dd>
  <dd>列表3</dd>
  <dt>标题</dt>
  <dd>列表1</dd>
  <dd>列表2</dd>
  <dd>列表3</dd>
  <dt>标题</dt>
  <dd>列表1</dd>
  <dd>列表2</dd>
  <dd>列表3</dd>
</dl>
dl > :nth-child(even of dd) {
   color: red;
font-weight: bold;
}
表示匹配所有子元素集合中的偶数项的 <dd> 元素,此时在支持此语法的浏览器下可以看到类似下图的效果。


图 6 :nth-child伪类中的of匹配语法的渲染

顺便提一下,选择器 :nth-child(even of dd) 和选择器 dd:nth-child(even) 是不一样的,例如下面的 CSS 代码匹配的是 <dl> 元素下偶数项同时标签是 dd 的子元素,even 关键字是相对整个子元素而言的,而不是仅相对于 <dd> 元素。
dl > dd:nth-child(even) {
   color: red;
   font-weight: bold;
}
此时的渲染效果如下图所示。


图 7 :nth-child伪类无of参数语法的渲染

另外,of 后面的选择器除了标签选择器,类名选择器、属性选择器都是支持的,可以让子元素的匹配更加精准可控。可惜这个特性目前仅被 Safari 浏览器支持,Chrome 等浏览器暂时没有支持的迹象,因此,大家先了解即可。

推荐阅读