
在 CSS 中,:nth-child() 是一个非常强大的选择器。它允许我们根据元素在父元素中的位置,按照一定的规律选择元素,例如 :nth-child(2n) 、:nth-child(3n + 1) 等。
不过,传统的 :nth-child() 有一个明显的限制:参数必须写死在选择器中。一旦页面结构或规则需要改变,我们就只能修改选择器本身,而无法像普通样式那样通过变量进行动态控制。
随着 CSS 新特性 的出现,这个限制开始被打破。借助 sibling-index() 、数学函数 以及 if() 条件表达式 ,我们可以在 CSS 中重新实现 :nth-child(An + B) 的匹配逻辑,并把 A 和 B 变成可动态修改的 CSS 变量。
这意味着我们不仅可以根据元素索引进行计算,还可以把匹配结果转换为可参与计算的变量值。这样一来,元素是否符合 :nth-child() 规则,就不再只是一个选择器条件,而是可以直接影响颜色、尺寸、动画甚至布局。
接下来,我们将一步一步构建这个逻辑。先理解 :nth-child(An + B) 的数学原理,然后利用 sibling-index() 获取元素索引,再结合 CSS 三角函数和 if() 条件判断,实现一个完全由 CSS 驱动的动态 :nth-child() 方案。最终,你将能够创建一种新的模式:用变量控制元素选择逻辑,而不是写死选择器。
回顾 :nth-child(An + B) 选择器
在传统 CSS 中,如果想给符合 :nth-child(An + B) 规则的元素应用样式,通常会这样写:
CSS
li {
border: 4px solid;
&:nth-child(An + B) {
color: red;
}
}
我们将上面代码中的 A 和 B 替换为具体的数值,比如:
CSS
li {
border: 4px solid;
&:nth-child(3n + 1) {
color: red;
}
}

Demo 地址:codepen.io/airen/full/...
这种方式虽然简单,但 A 和 B 的值是固定在选择器中的,一旦需要改变规则,就必须修改选择器本身,无法通过变量进行动态控制。
:nth-child(An + B) 的数学原理
:nth-child() 的核心其实是一个简单的数学序列公式。它用于根据元素在父元素中的位置(索引)选择符合某种规律的元素:
CSS 中的公式形式为:
css
An + B
其中包含三个关键部分:
-
A:步长,决定选择元素的间隔 -
n:从0开始递增的整数序列 (0,1,2,3, ...) -
B:起始偏移量
也就是说,:nth-child(An + B) 实际上会生成一个索引序列:
css
B, A + B, 2A + B, 3A + B, ...
只要元素的索引值等于这个序列中的某一项,该元素就会被匹配。
需要注意的是,:nth-child() 使用的是从 1 开始的索引,而不是编程中常见的从 0 开始。例如:
CSS
li:nth-child(1) {
color: blue;
}
只会匹配第一个 li 元素:

理解 :nth-child(An + B) 最直观的方法,是把元素的位置想象成一个编号格子,每个元素都有一个从 1 开始的索引:

:nth-child(An + B) 的作用,就是按照某种数学规律在这些格子中进行选择。
刚才提到过,:nth-child(An + B) 中的 A 是一个步长,表示每隔多少个元素选择一次。例如:
CSS
li:nth-child(3n){
color: red;
}
当 n = 0,1,2,3... 时:
ini
3n = 0, 6, 9, 12, 15...
因为 0 不是有效索引,所以实际匹配:
erlang
3, 6, 9, 12, 15...
也就是每 3 个 li 元素选一个:

:nth-child(An + B) 中的 B 决定"起点偏移",用来控制从第几个元素开始。例如:
CSS
li:nth-child(3n + 1) {
color: red;
}
公式是 3n + 1 ,当 n = 0,1,2,3... 时:
ini
3n + 1 = 1, 4, 7, 10

可以理解为:从第 1 个 li 开始,每隔 3 个选一个。
也就是说,:nth-child(An + B) 中的 An + B 实际生成的是一个序列。当 n = 0,1,2,3... 时,An + B 会生成:
css
An + B = B, A+B, 2A+B, 3A+B ...
例如 :nth-child(4n + 2) 选择器,其 A = 4 ,B=2 ,它产生的序列是 2, 6, 10, 14... :

你可能注意到了,n 是从 0 开始的。或许你会问,为什么 n 要从 0 开始?原因很简单,就是为了保证公式统一。例如 2n + 1 ,当:
ini
n = 0 → 1
n = 1 → 3
n = 2 → 5
正好得到:1, 3, 5, 7... 。
需要知道的是,:nth-child(An + B) 中的 A 也可以是负数,例如:
CSS
li:nth-child(-n + 5) {
color: red;
}
序列为 5, 4, 3, 2, 1 ,将会匹配第 1, 2, 3, 4, 5 个 li 元素。也就是前 5 个元素:

这也是一个常见技巧。
正如你所看到的,:nth-child(An + B) 的关键是数学判断条件。如果某个元素的索引为 k ,要判断它是否匹配 An + B ,本质上就是判断是否存在某个整数 n 使用得:
ini
k = An + B
等价于:
css
n = (k - B) / A
当 n 是非负整数 时,该元素就会被 :nth-child(An + B) 匹配。这正是我们在动态实现 :nth-child() 时所利用的数学基础。
动态 :nth-child()
使用 :nth-child(An + B) 可以很方便地实现基于位置的范围选择。不过,这种方式也有一个明显的限制:A 和 B 的值是固定写在选择器中的。一旦需要改变选择规则,就必须修改选择器本身,而无法像普通 CSS 属性那样通过变量进行动态控制。
随着 CSS 新特性的出现,这个限制开始被打破。借助 sibling-index()、数学函数以及 if() 条件表达式,我们可以在 CSS 中重新实现 :nth-child(An + B) 的匹配逻辑,并将 A 和 B 变成 可动态修改的 CSS 变量。
也就是说,我们可以把 :nth-child(An + B) 的判断逻辑转化为 CSS 计算过程。例如:
CSS
@property --g {
syntax: "<number>";
inherits: false;
initial-value: 0;
}
ul {
--A: ;
--B: ;
li{
--n: calc((sibling-index() - var(--B)) / var(--A));
--g: calc(sign(var(--n) + .0001) + var(--n) - round(down, var(--n)));
/* 当 --g 等于 1 时,该元素匹配 :nth-child(An + B) */
color: if(style(--g: 1): red; else: blue);
}
}
在这段代码中,--A 和 --B 被定义为 CSS 变量,对应 :nth-child(An + B) 公式中的 A 和 B。通过 sibling-index() 获取当前元素的索引,再结合数学计算,就可以判断该元素是否符合 An + B 的规律。计算结果最终会存储在变量 --g 中:当元素符合规则时,--g 的值为 1;否则为 0。随后我们就可以通过 if() 根据这个结果应用不同的样式。
CSS
@property --g {
syntax: "<number>";
inherits: false;
initial-value: 0;
}
ul {
--A: 3;
--B: 2;
li{
--n: calc((sibling-index() - var(--B)) / var(--A));
--g: calc(sign(var(--n) + .0001) + var(--n) - round(down, var(--n)));
/* 当 --g 等于 1 时,该元素匹配 :nth-child(An + B) */
color: if(style(--g: 1): red);
}
}

Demo 地址:codepen.io/airen/full/...
这样做的最大意义在于:A 和 B 现在可以动态修改 。由于它们是 CSS 变量,我们可以在不同作用域中改变它们的值,而无需改动选择器本身,这是传统 :nth-child() 无法做到的。

Demo 地址:codepen.io/airen/full/...
从原理上看,An + B 中的 n 必须是非负整数(包括 0)。因此在计算中需要完成两个判断:一是确认 n 是否为正数或 0,二是确认 n 是否为整数。
首先:
css
sign(var(--n) + .0001)
这一步用于判断 --n 是否为正数。当 --n 为正数时结果为 1,否则为 -1。这里额外加上 .0001 的原因,是为了让 0 也被视为有效值,从而符合 n ≥ 0 的条件。
接下来:
css
var(--n) - round(down, var(--n))
这一部分用于判断 --n 是否为整数。如果 --n 是整数,结果为 0;如果不是整数,则会得到一个 0 到 1 之间的小数。
当这两部分的计算结果相加等于 1 时,就说明当前元素满足 An + B 的规则,也就相当于匹配了 :nth-child(An + B)。通过这种方式,我们就能在 CSS 中实现一个可计算、可动态调整的 :nth-child() 逻辑。
封装成一个函数
我们可以使用函数语法来封装前面实现 :nth-child(An + B) 的计算逻辑,从而创建一个更简洁、可复用3、易读的写法。通过 自定义 CSS 函数 (@function),可以把复杂的计算隐藏在函数内部,在使用时只需要传入 A 和 B 两个参数即可。
CSS
@function --nth-child(--A:1, --B:0, --_g <number>: 0) {
--n: calc((sibling-index() - var(--B))/var(--A));
--_g: calc(sign(var(--n) + .0001) + var(--n) - round(down,var(--n)));
result: if(style(--_g: 1): 1;else: 0);
}
li {
--g: --nth-child(-2,10);
/* 当元素被选中时 --g 等于 1,否则为 0 */
color: if(style(--g: 1): red;);
}

Demo 地址:codepen.io/airen/full/...
在这个示例中,--nth-child() 是一个自定义 CSS 函数,用来模拟 :nth-child(An + B) 的匹配逻辑。函数接收两个参数:--A 和 --B,分别对应公式中的 A 和 B。函数内部会根据 sibling-index() 计算当前元素在同级元素中的位置,并判断该元素是否满足 An + B 的规则。如果满足条件,函数返回 1;否则返回 0。
在实际使用时,我们只需要调用函数即可,例如:
css
--g: --nth-child(-2,10);
这相当于匹配 :nth-child(-2n + 10) 。函数返回的结果会被存储在 --g 变量中:当元素符合选择条件时,--g 的值为 1;不符合时则为 0。
有了这个变量,我们就可以很方便地控制不同的样式。例如在示例中,当 --g 为 1 时,元素背景变为红色,否则为初始色。这种方式可以把选择逻辑直接转化为可计算的变量值,从而在 CSS 中实现更灵活的动态样式控制。
小结
在这节课中,我们从 :nth-child(An + B) 的基础原理出发,理解了它背后的数学规律:通过 An + B 生成一个位置序列,从而根据元素在父元素中的索引选择符合规则的元素。这种方式在 CSS 中非常常见,例如实现奇偶行样式、间隔布局或规则性的元素选择。
不过,传统的 :nth-child() 也存在一个限制:A 和 B 的值必须写死在选择器中,一旦需要调整规则,就必须修改选择器本身,缺乏灵活性。
为了解决这个问题,我们借助 CSS 的新特性------sibling-index()、数学函数以及 if() 条件表达式,在 CSS 中重新实现了 :nth-child(An + B) 的匹配逻辑。通过将 A 和 B 定义为 CSS 变量,并利用计算判断元素是否符合规则,我们可以把原本固定在选择器中的逻辑转化为可计算、可动态调整的变量结果。
这种方法带来的最大优势是:元素是否被"选中"不再只是选择器的结果,而是一个可以参与计算的变量值。这样不仅可以控制颜色或样式,还可以进一步用于动画、布局或其他 CSS 计算,从而实现更加灵活和动态的界面效果。
需要注意的是,这些功能仍然属于较新的 CSS 能力,目前浏览器支持还比较有限(主要在 Chrome 中可用)。不过,它们展示了一种新的思路:通过 CSS 计算来表达原本只能由选择器完成的逻辑。
掌握这种思路之后,你不仅可以实现动态的 :nth-child() 规则,还能更深入地理解 CSS 计算体系,并在未来探索更多基于变量和数学函数的高级样式技巧。