写过动画的人都踩过这个坑——每次写交错动画都要给每个 li 配 nth-child,今天这件事被两个 CSS 函数彻底原生化了

每次写列表的交错动画,都要给每个 li 配一行 nth-child,再算 delay——这件事今天被两个 CSS 函数彻底原生化了。

CSS Values and Units Module Level 5 定义了两个新函数:sibling-index() 返回元素在兄弟节点中的位置(从 1 开始),sibling-count() 返回同级兄弟的总数。它们从 2026 年 8 月 18 日正式进入 Baseline,Chrome 138+、Safari 26.2+、Firefox 154+ 支持,全球约 70% 覆盖率。

一个痛点被彻底填平

以前要实现列表的交错入场动画,通常有三个选择:给每个元素单独写一条 nth-child 规则,给 JS 遍历 DOM 设置 –index 自定义属性,或者装一个动画库。

以 5 个列表项为例,nth-child 方案是这样的:

li:nth-child(1) { --i: 0; }
li:nth-child(2) { --i: 1; }
li:nth-child(3) { --i: 2; }
li:nth-child(4) { --i: 3; }
li:nth-child(5) { --i: 4; }

li {
  transition-delay: calc(0.1s * var(--i));
}

如果列表长度变成 50 个,这套规则就要写 50 行。而且每次增删元素,都要同步改 CSS。

现在一个声明就搞定:

li {
  animation: slide-in 0.5s ease-out forwards;
  animation-delay: calc((sibling-index() - 1) * 120ms);
}

这个 -1 是关键:sibling-index() 从 1 开始计数,第一个元素的 delay 应该是 0 而不是 120ms。减 1 才能让第一个元素立即开始。

不只是动画

因为返回值是整数,calc() 能直接用,这两个函数的场景远不止 stagger 动画。

等分容器宽度——每个 tab 均分:

.tab {
  width: calc(100% / sibling-count());
}

渐变色相:每个元素按位置分配不同的背景色:

.swatch {
  background-color: hsl(
    calc((360deg / sibling-count()) * sibling-index())
    70% 50%
  );
}

扇形布局:配合 CSS 三角函数把元素围成一圈:

.card {
  --angle: calc((360deg / sibling-count()) * sibling-index());
  transform: rotate(var(--angle)) translateY(-120px);
}

倒序 stagger:让最后一个元素先走:

.card {
  animation: card-out 300ms ease both;
  animation-delay: calc((sibling-count() - sibling-index()) * 80ms);
}

注意两个边界

第一,sibling-index() 和 sibling-count() 只统计直接子元素,不穿透 Shadow DOM。从 shadow-root 外部使用这两个函数会返回 0,这是浏览器的安全隔离,不是 bug。

第二,::before 和 ::after 不是真实 DOM 节点,不会被计入 sibling 数量,但可以反过来使用这两个函数——它们会基于原始元素的位置来计算。

渐进增强怎么做

Firefox 154 目前是 Preview support,部分场景可能不稳定。用 @supports 做回退:

.card {
  animation: card-in 400ms ease both;
  animation-delay: calc((sibling-index() - 1) * 120ms);
}

@supports (animation-delay: calc(sibling-index() * 1ms)) {
  /* 这块在不支持的浏览器里会被跳过 */
}

不支持的浏览器会直接忽略不识别的 calc 表达式,fallback 到无 delay 的默认动画,不会破坏布局。

下一步

如果你的列表 stagger 动画还在靠 nth-child 规则或 JS 遍历,现在可以抽掉这段逻辑了。先在列表项上跑一行 animation-delay: calc((sibling-index() - 1) * 100ms),看看效果——如果列表长度固定,nth-child 还能接受;如果列表是动态的,这是必迁。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 976 阅读