你以为 nth-child() 只能选元素?今天 CSS 用两个计数函数把这件事彻底原生化了
做过动画 stagger、算过列表进度条、调过色相分布的前端工程师,大概都踩过同一个坑——浏览器明明知道每个元素是第几个,CSS 却从来没法直接问它要这个数。只能靠 JS 注入手动变量,或者 Sass 循环生成一堆 :nth-child() 规则。今天这件事被两个 CSS 函数彻底原生化了。
这个痛点到底有多普遍
举个最常见的场景:你有 10 个卡片要做交错入场动画。传统做法是 JS 遍历所有卡片,手动注入 –index 变量:
const cards = document.querySelectorAll(“.card”);
cards.forEach((card, index) => {
card.style.setProperty(“–index”, index + 1);
card.style.setProperty(“–count”, cards.length);
});
然后 CSS 才能用:
.card {
animation-delay: calc(var(–index) * 100ms);
}
这代码不复杂,但它有几个问题:列表长度变了要改 JS、组件重构了可能忘记这个变量、列表是动态的时候还要配合 MutationObserver。说到底,这不是 JS 该管的事——浏览器在构建 DOM 树的时候就已经知道答案了。
CSS 终于开口了:sibling-index() 和 sibling-count()
这是 CSS Values and Units Module Level 5 规范的两个新函数,Chrome 138+ 和 Safari 26.2+ 已经支持,全球约 75% 覆盖率。
sibling-index() 返回元素在父元素所有子节点中的位置(第 1 个返回 1,第 5 个返回 5)
sibling-count() 返回父元素共有多少个子元素
两者都返回整数而不是字符串,可以直接扔进 calc() 做数学运算。
所以上面的 stagger 动画,JS 全删掉,一行 CSS 搞定:
.card {
animation-delay: calc(sibling-index() * 100ms);
}
5 个卡片也好,500 个卡片也好,浏览器自动算好每个的延迟,列表长度变了 CSS 自动重新算。
和 :nth-child() 根本不是一回事
很多人第一反应是「这不就是 :nth-child() 吗」,但两者解决的是完全不同的问题:
:nth-child() 是选择器——挑出某个位置的元素,不能参与计算
sibling-index() 是值函数——返回当前位置的数字,可以放进 calc()
你没法写 calc(:nth-child(3) * 10px),因为 nth-child 选完就完了,不产生数字。但 sibling-index() 返回的本身就是数字,扔进 min()、max()、round()、sin()、cos() 随便玩。
实战场景:从进度条到色相分布
场景一:自动等宽网格。不用数有几个子元素:
.tab {
width: calc(100% / sibling-count());
}
5 个 tab 各自 20%,加一个变成 6 个自动变成 16.66%,删两个变成 25%。
场景二:进度指示器。每个步骤知道自己在第几步:
.step {
–progress: calc(sibling-index() / sibling-count());
}
场景三:色相反馈。把 10 个色卡从 0deg 到 360deg 均匀分布:
.swatch {
background-color: hsl(
calc((360deg / sibling-count()) * sibling-index())
70% 50%
);
}
场景四:倒序 stagger。最后一个先入场:
.card {
animation-delay: calc((sibling-count() – sibling-index()) * 80ms);
}
写在最后
这两个函数本质上是把「浏览器已经知道的信息」还给了 CSS。在此前,工程师要么靠 JS 注入手动变量,要么靠 Sass 循环生成一坨选择器规则——两种都是 workaround。
现在你只需要问浏览器要这个数字,它本来就记得。
评论区
登录后可评论。