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 了。

评论区

0 条评论

登录后可评论。