写了八年列表动画,今天才发现 CSS 从来不需要 JS 帮你算 index
做过列表入场动画的工程师,大概都踩过这个坑——想给每个卡片错开 100ms 延迟,第一张 0ms,第二张 100ms,第三张 200ms……以前只有三种解法:模板里手写 data-index、手写 nth-child 规则链、或者让 JS 在挂载时遍历一遍往每个元素写 style。哪种都不优雅,哪种都要占代码量。
Chrome 138 和 Safari 26.2 彻底把这件事原生化了。CSS 现在自己认识「我是列表里第几个」和「列表里总共有几个」。
两个新函数的用法
sibling-index() 返回元素在自己的父级里是第几个,从 1 开始数。sibling-count() 返回父级里一共有几个孩子(包括自己)。它们可以当整数直接用,但最强大的是塞进 calc() 里做运算。
/* 列表入场动画,自动按顺序错开 */
.card {
animation: fadeIn 0.4s ease both;
animation-delay: calc((sibling-index() - 1) * 80ms);
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
三行代码,第一个卡片立即播放,第二个等 80ms,第三个等 160ms,以此类推。增减列表项不需要改任何 CSS,浏览器自己会重新算。
再看 sibling-count() 的用法——平均分列:
.tag {
width: calc(100% / sibling-count());
}
无论一行塞了几个 tag,每个都等宽。删掉一个,剩下自动撑满整行。
或者做色相均匀分布的标签云:
.tag {
background: hsl(
calc(360 / sibling-count() * (sibling-index() - 1)),
70%, 55%
);
}
三个 tag 分别拿到 0°、120°、240° 的背景色,六个 tag 均分 360°,不用改一行 JS。
进阶用法:z-index 堆叠与进度指示器
堆叠卡片时让第一张在最上:
.card {
z-index: calc(sibling-count() - sibling-index());
}
做进度步骤条指示器:
.step {
width: calc(100% / sibling-count());
}
.step.active::after {
content: "";
position: absolute;
bottom: 0;
left: calc(100% / sibling-count() * (sibling-index() - 1));
width: calc(100% / sibling-count());
height: 3px;
background: var(--accent);
}
一个要记的坑:display:none 还会被数进去
sibling-index() 和 sibling-count() 读的是 DOM 树,不是渲染树。display: none 的元素视觉上消失了,但还占着序号。如果你用 display: none 过滤列表项,动画延迟会出现「跳号」—— 第三个可见元素拿到的是 index: 3 而不是 2。
解法:过滤时把元素真正从 DOM 移除,或者改用 visibility: hidden + opacity: 0(这两个属性不改变计数,且更符合「隐藏但占位」的语义)。
渐进增强:Firefox 还没上
截至 2026 年 9 月,Chrome 138+、Safari 26.2+ 已支持,Firefox 还在开发中(Bugzilla #1953973)。生产环境推荐渐进增强:
/* 兜底:所有卡片同一个 delay */
.card { animation-delay: 0ms; }
/* 支持的浏览器逐步增强 */
@supports (z-index: sibling-index()) {
.card {
width: calc(100% / sibling-count());
animation-delay: calc((sibling-index() - 1) * 80ms);
}
}
这样没支持的浏览器拿到正常体验,支持的拿到完整动画,不需要任何 JS。
下一步
如果你现在在做列表动画或者需要动态均分布局,先去 caniuse 确认目标用户的浏览器覆盖情况,然后把手头那段「挂载时 JS 算 index 然后写进 style」的代码删掉,换成上面几行 CSS。性能上,浏览器在 cascade 阶段就算好了,比运行时 JS 循环写 style 再触发一次重排要干净得多。
评论区
登录后可评论。