返回效果库

列表错落入场

Staggered List

一组内容,一件接一件落到位置上

完整版:适合从零新建组件,规格、契约、验收全都在。

实时预览

布局

RECENT ACTIVITY

最近活动

30ms · 320ms · 12px · cap 8

  • 构建通过09:12
  • 提示词已导出09:08
  • 设计令牌已同步08:54
  • 视觉验收通过08:31
  • 组件已合并08:02
  • 预览环境已部署07:45

重播 = 用新的 key 重新挂载列表

调整参数观察布局动效检测到「减少动态效果」,动效已降级

参数

边调边看,提示词同步更新。

角色

你是一名资深前端工程师,工作在现有的项目中,技术栈是 React 19 + TypeScript + Tailwind CSS v4,入场用纯 CSS keyframes,不引入动画库。只实现下面这一个微交互:不要加额外功能,不要顺手重构周围代码。

任务

实现列表错落入场:列表首次挂载时,每一行从下方 12px 处用 320ms、cubic-bezier(0.2, 0.8, 0.2, 1) 缓动做一次淡入上升;相邻行相差 30ms,延迟在第 8 项之后封顶。每次挂载只播一次——父组件重渲染不能重播,重播必须重新挂载列表。

文件契约

- 只新建一个文件:components/effects/StaggerList.tsx。

- 依赖:无需额外依赖:keyframes 由组件内的 <style> 注入,逐行动画只依赖 CSS 变量。

- 不要修改 package.json、全局 CSS、Tailwind 配置或任何其他已有文件。

实现要点

- 文件顶部加 "use client";在组件里用 <style> 注入 stagger-list-in keyframes,并在 prefers-reduced-motion: reduce 里用 !important 关掉动画与位移。

- 导出 StaggerList(props:items、stagger、duration、rise、cap、className、style、label)与纯函数 staggerStyle(index, step = 30, max = 8),后者返回 { animationDelay: Math.min(index, max) * step + "ms" }。

- 把 staggerStyle(index, stagger, cap) 的结果放到每行的行内 style;时长与上浮距离通过 CSS 变量 --stagger-duration / --stagger-rise 从 ul 统一下发,改 props 不用改 keyframes。

- 每行 animation: ... both,首帧即初始态;只动 transform 与 opacity,绝不逐帧改 width / height / top / left。

- 用一个 useRef 记录是否已经播完:最后一行 animationend 后置位并移除动画 class,父组件重渲染不会重播;reduced-motion 时挂载即置位。

规格 —— 精确数值,不许四舍五入或“优化”

- 每行动画:opacity 0 → 1,transform translateY(12px) → translateY(0),时长 320ms,缓动 cubic-bezier(0.2, 0.8, 0.2, 1),animation-fill-mode: both(第一帧就已经是初始态)。

- 每行延迟:30ms × min(index, 8)。index 从 0 开始,所以第一行延迟 0ms,从第 8 项起都以 240ms 封顶。

- 只允许动画 transform 与 opacity;行不允许动画 width、height、margin、top、left。

- 列表几何:纵向列表,行间距 8px;每行是带边框的卡片,圆角 10px,上下内边距 8px、左右 12px,左侧 6px 的 lime 圆点,标题字号 12.5px,右侧可选 10px 等宽小标签。

- 重播等于重新挂载:一次挂载只播一次,父组件重渲染不重播,只有换 key / 重建 DOM 才重播。

- 行必须是真实 <li>,外层是 <ul>,列表要有可访问名称。

行为

- 入场是一次性的:未播放时行带动画 class;最后一行 animationend 触发后标记已播放并移除动画 class。父组件重渲染不允许重播入场。

- 延迟用 min(index, cap) × stagger 手算出来,写进行的行内 animation-delay;绝不能用链式 setTimeout 逐行启动。

- 重播由组件外部控制(重新挂载 / 新 key);不要把重播方法做进组件本身。

- prefers-reduced-motion: reduce:不播 keyframes、不位移——首帧就是 opacity: 1 / translateY(0),行内 animation-delay 也一并去掉。

- 行本身不可交互,保持语义化,不要把整行做成按钮。如果演示需要「重播」按钮,必须是真实 <button type="button">,可 Tab 聚焦,Enter / Space 可触发,focus-visible 显示 2px #C9F45A、外扩 3px 的描边。

- 卸载时清理;不要在 document/window 上绑监听,不要每帧触发状态更新;行内 <style> 是唯一副作用,必须留在组件内部作用域。

禁止

- 不要用动画改变 width / height / top / left / margin / box-shadow。

- 不要用链式 setTimeout 启动行,也不要从 JavaScript 逐帧改 opacity / translateY。

- 不要留 TODO、不要写 // 其余代码、不要用 any、不要 console.log、不要留注释掉的实验代码。

- 不要把组件包进页面、布局、演示壳或 Provider。

- 不要改动全局 CSS、Tailwind 配置、package.json 或上面文件之外的任何文件。

- 不要引入任何动画库;入场必须是用行内变量驱动的纯 CSS keyframes。

验收 —— 回答前逐项自查

1. 每行的时长、上浮距离、行间隔与延迟封顶值都和当前参数完全一致(默认 320ms / 12px / 30ms / 8),延迟严格等于 min(index, cap) × stagger。

2. 在入场后 60–80ms 采样,已经开始的行都严格处于起点与终点之间(opacity 0–1、translateY 0–rise),没有跳帧或先闪出最终态。

3. 最后一行 animationend 后动画 class 被移除、transform 归零,不残留内联 animation-delay 或 style。

4. 整个入场过程中 ul 的 boundingBox 完全不变,只有 transform / opacity 变化,没有布局抖动。

5. prefers-reduced-motion 下不播动画、没有位移,列表在首帧就是最终态。

6. 「重播」按钮可聚焦,Enter / Space 能触发重新挂载;焦点环可见(2px lime,外扩 3px)。

7. 控制台 0 报错 / 警告;父组件重渲染不会重播;卸载后没有残留的 <style> 或定时器。

输出

- 用一个代码块输出完整文件内容,不要 diff、不要省略号,代码块之前不要解释。

- 如果缺少必要依赖,先停下来问我,不要擅自安装。

- 代码块之后最后输出一行:self-check: 1 ✓ · 2 ✓ · 3 ✓ · 4 ✓ · 5 ✓ · 6 ✓ · 7 ✓,未通过的项用 ✗ 并附一句原因。

与演示同源生成3,171 字符 · 881 约 tokens · 55 行
技术规格

列表第一次出现时,每一行用 320ms 做一次 fade-up:透明度从 0 到 1,位置从下方 12px 回到 0,缓动 cubic-bezier(0.2, 0.8, 0.2, 1),fill-mode both 保证第一帧就是初始态,不会先闪出最终态。相邻两行相差 30ms,延迟在第 8 项之后封顶,长列表不会越等越久。入场一个挂载周期只播一次,父组件重渲染不会重播;要重播就得重新挂载列表。

行间隔
30 ms
单项时长
320 ms
上浮距离
12 px
延迟封顶项数
8
逐项延迟上限
240ms
6 行总时长
470ms
缓动
cubic-bezier(0.2, 0.8, 0.2, 1)
重播方式
用新 key 重新挂载列表
验收清单

提示词要求 AI 在输出末尾逐条自检。

  1. 每行的时长、上浮距离、行间隔与延迟封顶值都和当前参数完全一致(默认 320ms / 12px / 30ms / 8),延迟严格等于 min(index, cap) × stagger。
  2. 在入场后 60–80ms 采样,已经开始的行都严格处于起点与终点之间(opacity 0–1、translateY 0–rise),没有跳帧或先闪出最终态。
  3. 最后一行 animationend 后动画 class 被移除、transform 归零,不残留内联 animation-delay 或 style。
  4. 整个入场过程中 ul 的 boundingBox 完全不变,只有 transform / opacity 变化,没有布局抖动。
  5. prefers-reduced-motion 下不播动画、没有位移,列表在首帧就是最终态。
  6. 「重播」按钮可聚焦,Enter / Space 能触发重新挂载;焦点环可见(2px lime,外扩 3px)。
  7. 控制台 0 报错 / 警告;父组件重渲染不会重播;卸载后没有残留的 `<style>` 或定时器。

待回归验证提示词与演示已同源生成;尚未在真实 Cursor / Claude Code 项目中做回归。

提示词如何还原这个效果

不是把效果描写一遍,而是把效果写成一份可执行的工程需求。

数字规格
每个像素、每个时间参数都来自同一个参数源——滑块改什么,提示词就写什么。
文件契约
只允许新建指定文件、指定依赖白名单,避免它顺手改你的全局样式或装一堆包。
验收清单
提示词末尾强制 AI 自检布局位移、键盘可用、reduced-motion 等 7 项。
完整版 / 中文

React + Tailwind + CSS keyframes