使用 sibling-index() 和 if() 实现动态的 :nth-child()

在 CSS 中,:nth-child() 是一个非常强大的选择器。它允许我们根据元素在父元素中的位置,按照一定的规律选择元素,例如 :nth-child(2n):nth-child(3n + 1) 等。

不过,传统的 :nth-child() 有一个明显的限制:参数必须写死在选择器中。一旦页面结构或规则需要改变,我们就只能修改选择器本身,而无法像普通样式那样通过变量进行动态控制。

随着 CSS 新特性 的出现,这个限制开始被打破。借助 sibling-index()数学函数 以及 if() 条件表达式 ,我们可以在 CSS 中重新实现 :nth-child(An + B) 的匹配逻辑,并把 AB 变成可动态修改的 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;
    } 
}

我们将上面代码中的 AB 替换为具体的数值,比如:

CSS 复制代码
li {
    border: 4px solid;
    
    &:nth-child(3n + 1) {
        color: red;
    } 
}

Demo 地址:codepen.io/airen/full/...

这种方式虽然简单,但 AB 的值是固定在选择器中的,一旦需要改变规则,就必须修改选择器本身,无法通过变量进行动态控制。

: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 = 4B=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, 5li 元素。也就是前 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) 可以很方便地实现基于位置的范围选择。不过,这种方式也有一个明显的限制:AB 的值是固定写在选择器中的。一旦需要改变选择规则,就必须修改选择器本身,而无法像普通 CSS 属性那样通过变量进行动态控制。

随着 CSS 新特性的出现,这个限制开始被打破。借助 sibling-index()、数学函数以及 if() 条件表达式,我们可以在 CSS 中重新实现 :nth-child(An + B) 的匹配逻辑,并将 AB 变成 可动态修改的 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) 公式中的 AB。通过 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),可以把复杂的计算隐藏在函数内部,在使用时只需要传入 AB 两个参数即可。

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,分别对应公式中的 AB。函数内部会根据 sibling-index() 计算当前元素在同级元素中的位置,并判断该元素是否满足 An + B 的规则。如果满足条件,函数返回 1;否则返回 0

在实际使用时,我们只需要调用函数即可,例如:

css 复制代码
--g: --nth-child(-2,10);

这相当于匹配 :nth-child(-2n + 10) 。函数返回的结果会被存储在 --g 变量中:当元素符合选择条件时,--g 的值为 1;不符合时则为 0

有了这个变量,我们就可以很方便地控制不同的样式。例如在示例中,当 --g1 时,元素背景变为红色,否则为初始色。这种方式可以把选择逻辑直接转化为可计算的变量值,从而在 CSS 中实现更灵活的动态样式控制。

小结

在这节课中,我们从 :nth-child(An + B) 的基础原理出发,理解了它背后的数学规律:通过 An + B 生成一个位置序列,从而根据元素在父元素中的索引选择符合规则的元素。这种方式在 CSS 中非常常见,例如实现奇偶行样式、间隔布局或规则性的元素选择。

不过,传统的 :nth-child() 也存在一个限制:AB 的值必须写死在选择器中,一旦需要调整规则,就必须修改选择器本身,缺乏灵活性。

为了解决这个问题,我们借助 CSS 的新特性------sibling-index()、数学函数以及 if() 条件表达式,在 CSS 中重新实现了 :nth-child(An + B) 的匹配逻辑。通过将 AB 定义为 CSS 变量,并利用计算判断元素是否符合规则,我们可以把原本固定在选择器中的逻辑转化为可计算、可动态调整的变量结果

这种方法带来的最大优势是:元素是否被"选中"不再只是选择器的结果,而是一个可以参与计算的变量值。这样不仅可以控制颜色或样式,还可以进一步用于动画、布局或其他 CSS 计算,从而实现更加灵活和动态的界面效果。

需要注意的是,这些功能仍然属于较新的 CSS 能力,目前浏览器支持还比较有限(主要在 Chrome 中可用)。不过,它们展示了一种新的思路:通过 CSS 计算来表达原本只能由选择器完成的逻辑

掌握这种思路之后,你不仅可以实现动态的 :nth-child() 规则,还能更深入地理解 CSS 计算体系,并在未来探索更多基于变量和数学函数的高级样式技巧。

相关推荐
掘金安东尼1 小时前
低代码真的能替代前端吗?我看了 RollCode 的设计之后有点新想法
前端
IT_陈寒1 小时前
JavaScript开发者必知的5个高效调试技巧,比console.log强10倍!
前端·人工智能·后端
亿元程序员1 小时前
历时100天,亿元Cocos小游戏实战合集顺利完结!!!
前端
恋猫de小郭2 小时前
Flutter Beta 版本引入 ScrollCacheExtent ,并修复长久存在的 shrinkWrap NaN 问题
android·前端·flutter
Liu.7742 小时前
vscode前端实用插件
前端·vscode
HWL56792 小时前
使用CSS实现,带有动态浮动高亮效果的导航菜单
前端·css
GISer_Jing2 小时前
AI Agent技能Skills设计
前端·人工智能·aigc·状态模式
小江的记录本2 小时前
【PageHelper】 【Spring Boot + MyBatis + PageHelper】 完整项目示例+PageHelper核心原理深度解析
java·前端·spring boot·后端·sql·spring·mybatis
JamesYoung79712 小时前
第九部分 — 打包、调试和发布 发布前的打包与发布检查清单(Chrome 应用商店)
前端·chrome