nth-child 写的动画,每次加一个元素都要回头改 delay——sibling-index() 自己会算位置,把这件事彻底原生化了
做列表动画最痛苦的不是写动画本身,是每次往列表里多加一个元素,回头就要改一遍 transition-delay——nth-child(1)、nth-child(2)、nth-child(3) 写死在那里,列表一长就成了维护噩梦。这个问题从 CSS 诞生起就存在,今天终于被浏览器自己解决了。
sibling-index() 和 sibling-count() 是 Firefox 154(2026-08-18)正式支持的 CSS 函数,上周随同 Chromium 138~154 全家桶一起拿到了 Baseline 2026 “Newly Available” 徽章。Chrome 138+、Edge 138+、Safari 26.2+、Firefox 154+ 全部支持,全球覆盖率 79.6%,主流浏览器全部踩过线。
nth-child 的困境到底有多痛
做交错动画(staggered animation)的时候,最标准的写法是这样的:
li:nth-child(1) { transition-delay: 0ms; }
li:nth-child(2) { transition-delay: 100ms; }
li:nth-child(3) { transition-delay: 200ms; }
li:nth-child(4) { transition-delay: 300ms; }
看起来没问题,但现实是这样:
- 设计师说”列表再加一个”,你改 CSS
- 产品说”首屏只显示 3 条,后面懒加载”,你改 CSS
- 运营说”第一个不要动画”,你改 CSS,还要记得 nth-child 的偏移量全变了
每改一次列表结构,就要回头翻一遍 transition-delay 的硬编码。这就是 nth-child 写死的代价——它只能匹配”第几个”,不能表达”相对于兄弟们的位置”。
sibling-index() 怎么解决这个问题
sibling-index() 返回元素在所有兄弟节点中的位置,从 1 开始计数。把它直接塞进 calc() 里:
li {
opacity: 0;
transform: translateY(12px);
transition: opacity 0.3s, transform 0.3s;
transition-delay: calc((sibling-index() - 1) * 100ms);
}
/* 首帧初始状态 */
@starting-style {
li {
opacity: 1;
transform: translateY(0);
}
}
现在不管列表有多少个元素,不管中间插入了什么,delay 都会自动根据实际位置计算——加一个 li 就多 100ms,不需要改任何 CSS。
sibling-count() 解决的是什么问题
sibling-index() 告诉你”你在第几个”,sibling-count() 告诉你”总共有多少个”。这两个组合起来能做更精确的判断:
/* 最后一个元素间距清零 */
li:last-child {
margin-bottom: 0;
}
/* 兄弟少于 3 个时放宽间距 */
.container:has(> li:nth-last-child(n + 3)) {
gap: 24px;
}
/* 用 sibling-count() + calc() 做比例延迟 */
li {
transition-delay: calc(
(sibling-index() - 1) / (sibling-count() - 1) * 400ms
);
}
这样无论列表有 5 个还是 50 个元素,总延迟范围永远固定在 0ms~400ms 之间,不会因为多加了几个项目就把总时长拖到几秒。
实际场景对比
做卡片入场动画的时候,nth-child 写法:
.card:nth-child(1) { animation-delay: 0.0s; }
.card:nth-child(2) { animation-delay: 0.1s; }
.card:nth-child(3) { animation-delay: 0.2s; }
/* 超过 3 个继续手动加... */
sibling-index() 写法:
.card {
animation: fadeUp 0.5s ease-out both;
animation-delay: calc((sibling-index() - 1) * 100ms);
}
@keyframes fadeUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
5 个卡片、10 个卡片、动态列表——一套代码全搞定。
渐进增强
Firefox 154 才支持,Safari 16+、Chrome 138+ 支持,可以用 @supports 做降级:
.card {
/* 降级:全部同时动画 */
animation: fadeUp 0.5s ease-out both;
}
@supports (sibling-index()) {
.card {
animation-delay: calc((sibling-index() - 1) * 100ms);
}
}
不支持的浏览器会用同时动画的降级效果,不影响功能,只是不够”炫”。这个渐进策略对设计系统来说非常友好。
写在最后
sibling-index() 和 sibling-count() 的价值不只是一个新函数,而是 CSS 第一次真正意义上拥有了”获取自身在 DOM 中位置”的能力。之前的 nth-child、nth-of-type 都是”提前知道位置才能写选择器”,现在变成了”我写一套规则,浏览器根据实际位置计算”。
这个转变的意义类似于从”hardcode 配置值”到”声明式响应式”的跨越——下一次设计师再改列表长度的时候,你可以理直气壮地说:不用改 CSS 了。
评论区
登录后可评论。