Motion Replication Kit 最早面对的问题,看起来很像一道找不同。
先截一张页面初始状态,再滚动一段距离,截第二张。两张图放在一起,标题上移了,卡片出现了,背景位置也变了。证据似乎已经齐了,接下来只要把 opacity、translateY 和 duration 猜得像一点。
真正开始复刻以后才会发现,这两张图只证明了两个姿势。
它们没有告诉我中间发生了什么:元素是在 300ms 内结束,还是跟着滚动条走;先淡入再位移,还是两件事同时发生;五张卡片一起出现,还是按 80ms 错开;页面停在那个位置,是因为动画结束了,还是因为它被 pin 住了。
一个动效可以在起点和终点都对,动起来却完全不像。多少有点像只看两张剧照,认真复刻了一部电影。
所以我后来给这个工具定下的判断很简单:网页动效复刻首先是证据问题,之后才是 GSAP、CSS 或 Motion 的实现问题。
在第三帧里补回时间
早期 capture 流程保留 initial.png 和 scrolled.png。它很适合回答空间问题:滚动前后哪个元素可见,最终位置在哪里,页面有没有明显错位。
但项目自己的早期 PRD 也写得很直接:两个固定状态无法分析 scrub、pin 和多阶段滚动,截图本身也看不到 duration 与 easing。
于是 capture 后来没有寻找一张“更聪明的截图”,而是把证据拆开。
默认流程会沿滚动距离采五个时间点,保留 filmstrip;Playwright trace 记录页面在浏览器里发生过什么;需要时再录一段 video;initial.png 和 scrolled.png 继续留下,兼容旧流程,也方便快速比较前后状态。
这些文件不是同一种证据的重复备份。
截图负责空间,filmstrip 负责顺序,video 负责连续手感,trace 负责回到浏览器事件和时间线里排查。它们彼此有重叠,但没有谁能单独把动效讲完。
这也是我现在不太相信“给 Agent 一张参考图,它就能还原动效”这句话的原因。它或许能猜到一种合理的入场动画,但合理不等于原来的行为,更不等于已经被验证过。
每个推断都要带着回头路
有了多帧以后,下一步仍然不是直接生成代码。
工具会在每个采样点记录候选元素的位置、尺寸、透明度和 transform,也会读取 transition、animation、原生 scroll timeline,以及浏览器通过 Web Animations API 暴露的 duration、delay、easing 和 timeline 类型。随后,纯 Node 的推断层再根据位移、透明度差和进入步骤,给出 fade-up、slide-in、scale-in 或 stagger group 之类的候选解释。
这里最重要的设计不是“它能自动识别多少种动效”,而是所有写进 draft brief 的推断都带着同一句标记:(inferred — verify)。
这句看起来有点啰嗦,但我宁愿它一直碍眼。
浏览器能提供真实 timing 时,那是观测值;根据五个采样点算出位移方向,是推断;看到类名里有 motion,只能算线索。把三者压成同一种“AI 已识别结果”,会让后面的 Agent 在一个猜测上继续精确施工。
我更愿意让系统在证据不足时留下空白,而不是补出一个很像答案的数字。duration 猜错 100ms 也许只是手感差一点;把 scroll-linked 误判成一次性 reveal,整个交互关系都会变。
把行为映射回自己的组件
证据链变完整以后,另一个诱惑会出现:既然已经能看到目标页面,为什么不直接把它的 CSS、结构和资源一起搬过来?
Motion Replication Kit 的硬规则正好相反。参考页面只作为不可信证据;可以复刻 motion behavior,不能复制文案、图片、品牌资产、路由或页面结构。实现时使用中性命名和本地 data attribute,把观察到的行为重新映射到自己的组件。
这个限制不是为了把事情做难。它迫使我回答一个更有用的问题:目标项目真正需要保留的,到底是哪段行为?
如果只是一次 viewport reveal,CSS transition 加 IntersectionObserver 已经够了。需要零 JS 的 scroll progress,可以把原生 scroll-driven animation 当 progressive enhancement。只有在时间线、pin、scrub 或复杂序列真的存在时,才值得把 GSAP 和 ScrollTrigger 请进来。
技术越强,不一定越接近参考。一个原本 200ms 就结束的淡入,被做成可 scrub 的大时间线,只是把简单问题复刻得更贵。
所以工具不会从“检测到 GSAP”推出“目标项目也必须用 GSAP”。检测到的库是来源线索,不是实现命令。最终选择应该由行为复杂度、目标项目已有依赖、浏览器支持和 reduced-motion 方案一起决定。
让页面在 reduced motion 下再走一遍
很多动效复刻在“看起来像”之后就结束了。这个 kit 把结束线往后挪了一点。
每个动画都要有 prefers-reduced-motion 分支,而且 reduced-motion 不是把 duration 改成 0 就算交差。内容必须处于最终可见状态,不能因为 transform 或 opacity 的初始值卡在页面外面。
Capture 可以再跑一次 reduced-motion pass,compare 会根据两组 signal 判断参考页面是 honors、ignores、mixed 还是 unknown。这里故意没有在零依赖脚本里硬做 PNG 像素比较:为了比较图片引入解码依赖,会破坏工具保持零 runtime dependency 的边界。像素回归交给目标项目里的 Playwright toHaveScreenshot,JSON compare 只负责它能可靠判断的活动信号。
性能也一样。模板会在滚动采样期间观察 long task 和 CLS,并提供可以按项目调整的起始预算。它们不是“这个动效性能很好”的证明,只是一道门:复刻出来的手感不能靠阻塞主线程和推页面布局换来。
这层验证让“像”变成了几个更具体的问题:正常模式下顺序对不对,reduced motion 下内容还在不在,filmstrip 是否保留关键阶段,滚动时有没有明显长任务和布局偏移。
我最后会停在第三帧
我刚刚在当前仓库重新跑了一次测试:5 个测试通过,0 失败,0 跳过。它覆盖 capture 基线、几类 motion inference、stagger 推断、(inferred — verify) 标记和 MCP surface。
这个结果能证明工具链在这台机器上跑通,不能证明它已经理解所有网页动效。后者没有数据,我也不打算从五个绿色勾里变出来。
Motion Replication Kit 现在更像一套给人和 Agent 共用的取证流程。它保存状态,也记录时间;产出候选解释以后,还要回到 filmstrip 或 video 复核;实现完成以后,再用 reduced motion、视觉回归和性能门把结果收住。
所以我现在的最后一道检查很具体:打开 filmstrip,停在第三帧。它能不能说明元素还在过渡、已经被 pin 住,还是跟着滚动进度走?
如果答案仍然是 unknown,实现 brief 就继续留空。需要 video,就补 video;需要 trace,就回到 trace。第三帧过不了,代码先别动。
