← CSS Animation · 能力速览 / Scrubbable Stagger:单个进度值驱动错开动画 待审核 13 / 22
综合应用 · @function + sibling-index()

Scrubbable Stagger:单个进度值驱动错开动画

传统的错开动画 (staggered animation) 靠给每个元素叠不同的 animation-delay——但那是时间概念,只能正向播放,没法被一个外部进度「拖着走」。本页复刻 Frontend Masters 的一篇文章的思路:把「延迟」从时间维度改写成进度维度的偏移——整组动画只由一个标量 --m (0→1) 驱动,第 i 个元素在总进度里减去 i/k 的偏移就「起步更晚」。于是这组错开动画可被任意 scrub:接到滑块、接到滚动进度都行,正放、倒放、停在任意中间帧。封装手段是 CSS 的 @function at-rule,序号靠 sibling-index() 自动取得。

1 · 拖进度条:一个 --m 驱动整排级联

下面一排条的高度都由同一个 --stagger(var(--m), var(--k)) 算出。拖进度滑块改 --m,看级联前沿如何从左扫到右;拖密度 --k 看相邻条的重叠变多还是变少(k 越大、同时在动的条越多、过渡越平滑);勾上缓动则把每根的线性进度再过一道 --smoothstep()

每根条只调一次函数、--i 默认取 sibling-index(),写法与位置无关:

.bar {
  --p: --stagger(var(--m), var(--k));
  height: calc(var(--p) * 100%);
}

2 · 公式拆解

--stagger 内部是一段 clamp(0,,1)clamp(0, \dots , 1):先用 --lerp 把整体进度 --m 映射成一条扫过所有元素的「前沿」位置,再减去本元素的偏移 i/k,夹回 [0, 1] 就是这一根自己的进度。起点锚 (--u, --x) 与终点锚 (--v, --y) 决定前沿在 m=0 / m=1 两端落在哪——取默认值时,起始帧第一根刚要动、结束帧最后一根恰好满。

@function --lerp(--a, --b, --x) {
  result: calc(var(--a) + (var(--b) - var(--a)) * var(--x));
}

@function --stagger(
  --m, --k: 5, --i: sibling-index(),
  --u: 1, --x: 0, --v: sibling-count(), --y: 1
) {
  --q1: calc(var(--x) + var(--u) / var(--k));
  --q2: calc(var(--y) + var(--v) / var(--k));
  result: clamp(0, --lerp(var(--q1), var(--q2), var(--m)) - var(--i) / var(--k), 1);
}

各参数的含义:

参数 含义
--m 整体进度 [0, 1],唯一的驱动量;接滑块、接滚动、接 @keyframes 都行。
--k 错开密度——大致是「同时在动的元素数」。k 越大,相邻元素重叠越多、整体越平滑;k 越小越像逐个点亮。
--i 本元素的序号,默认 sibling-index() (从 1 起,自动按它在兄弟里的位置取得,无需手写)。
--u / --x 起点锚:在 --m = 0 时,第 --u 个元素恰好处于进度 --x。默认 1 / 0
--v / --y 终点锚:在 --m = 1 时,第 --v 个元素恰好处于进度 --y。默认 sibling-count() / 1

3 · 把 --m 接到滚动:同一个函数,换个驱动源

「可 scrub」的意义在这里显出来:上面用滑块喂 --m,这里把驱动源换成滚动进度——给 --m 写一段 @keyframes stagger-frames { to { --m: 1 } },再把它的时间线设成 scroll()。函数、HTML、每根条的写法一个字都不用改,级联就跟着滚动条走(往回滚即倒放)。底层机制见本系列 Scroll Timeline

@property --m { syntax: "<number>"; inherits: true; initial-value: 0; }

@keyframes stagger-frames { to { --m: 1; } }

.row {
  animation: stagger-frames linear both;
  animation-timeline: scroll(nearest);
}

现状提醒: 本页用到的两组特性进展并不同步。@function 出自 CSS Functions and Mixins 模块,仍属实验特性——仅 Chromium 139+(2025-08)落地,Firefox 与 Safari 均未实现。sibling-index() / sibling-count() 出自 CSS Values 5,已走得更远:Chromium 138+(2025-06)与 Safari 26.2+(2025-12)均已落地,Firefox 在 preview 阶段,规范上也不再标记为实验特性。因此真正的短板是 @function。本页在支持的浏览器跑真 @function + sibling-index() + animation-timeline: scroll();否则退回 JS 按同一公式计算每根条的高度(并在读数里标注)。探测用运行期探针(临时插一条 @function 看 computed 值),而非 @supports

4 · 相关链接