写了八年列表动画,今天才发现 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 再触发一次重排要干净得多。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 224 阅读