HyperFrames 时序移植指南:Remotion interpolate、spring 与 easing 到 GSAP 的完整映射
2026/9/12 5:08:13 网站建设 项目流程

HyperFrames 时序移植指南:Remotion interpolate、spring 与 easing 到 GSAP 的完整映射

【免费下载链接】hyperframesWrite HTML. Render video. Built for agents.项目地址: https://gitcode.com/GitHub_Trending/hy/hyperframes

导读:本文是 remotion-to-hyperframes 技能中最高杠杆的一份参考——在把 Remotion(React)合成移植为 HyperFrames(HTML + GSAP)时,缓动与时间轴是观众最先感知的差异,时序翻错比其他任何翻译选择损失的 SSIM 都更多。本文以 timing.md 为主体骨架,逐条讲解帧与秒的换算、interpolate线性/多段/自定义缓动、spring()弹簧、颜色与数字动画、stagger 错峰在 GSAP 时间线上的落地写法,并给出 T1–T3 分层语料实测的 SSIM 数据与源码级佐证。读完你将能够把任意 Remotion 的帧驱动动画准确翻译成可渲染、可验证的 HyperFrames 时间线。

为什么时序是移植中的最高优先级

HyperFrames 的时间线以为单位,而 Remotion 以为单位。两者之间的换算和缓动映射贯穿整个移植过程,直接影响渲染结果的像素级一致性。在 SKILL.md 定义的评估流程里,翻译结果会与 Remotion 基线渲染逐帧做 SSIM 对比:时序错误造成的画面差异,往往比其他任何翻译选择(布局、字体、媒体)都更显眼。因此本参考被标注为"single highest-leverage reference",并且已经过 T1–T3 分层语料的经验验证

层级合成形态实测平均 SSIM阈值
T1单元素淡入淡出0.9740.95
T2多场景 + spring + 音频 + 图片0.9850.95
T3数据驱动、自定义子组件、数字滚动0.9530.90

完整验证基线见 SKILL.md 的 validated baseline 表;SSIM 评估的完整流程见 eval.md。

换算基础:frames → seconds

HF 的时间线单位是秒,Remotion 基于帧。永远执行:

time_seconds = frame / fps

以 fps=30 为例:

  • frame 15 → 0.5 s
  • frame 30 → 1.0 s
  • frame 90 → 3.0 s

关键实践:在翻译时一次性完成换算,而不是在运行时换算。HyperFrames 的合成是"seek 驱动"的确定性模型,动画应被烘焙成一条暂停的 GSAP 时间线,帧计数在渲染时不应出现。

这条规则在 tier-1-title-card 的 HF 产物中直接可见——翻译注释明确写着"Frame ranges → time ranges: 0/30=0, 15/30=0.5, 75/30=2.5, 90/30=3.0",所有时间偏移都在生成 HTML 时计算完毕。

interpolate 线性插值 →ease: "none"

Remotion 最常见的单段线性插值:

const opacity = interpolate(frame, [0, 30], [0, 1], { extrapolateRight: "clamp" });

翻译为 GSAP:

gsap.to(target, { opacity: 1, duration: 1.0, ease: "none" }, 0); // 如果属性从 0 开始且 CSS 尚未设置初始值,使用 fromTo: gsap.fromTo(target, { opacity: 0 }, { opacity: 1, duration: 1.0, ease: "none" }, 0);

要点:

  • ease: "none"与 Remotion 默认的线性插值完全一致;
  • 如果初始状态已经写在 CSS 里,用gsap.to即可;否则必须用fromTo显式给出起始值。

关于extrapolate的边界行为:Remotion 中extrapolateLeft/extrapolateRight默认是"extend"(继续外推),但实际翻译中遇到最多的写法是"clamp"。GSAP 本身不会外推——tween 的起止值在动画前后保持恒定,天然等价于clamp。因此:

  • 源码用clamp→ GSAP 直接匹配,无需额外处理;
  • 源码用extend→ 需要在发射 HTML 前手动扩展输入范围(把 tween 的起始/结束时间按外推斜率继续延伸到需要的帧),GSAP 无法隐式表达。

interpolate 多段插值 → 时间线分段 tween

Remotion 的多段插值,如淡入→保持→淡出:

const opacity = interpolate(frame, [0, 15, 75, 90], [0, 1, 1, 0]);

在 GSAP 中翻译为三条时间线 tween,偏移量依次为[0]/fps[1]/fps[2]/fps(fps=30 时即 0 / 0.5 / 2.5 秒):

const tl = gsap.timeline({ paused: true }); tl.to(target, { opacity: 1, duration: 0.5, ease: "none" }, 0); tl.to(target, { opacity: 1, duration: 2.0, ease: "none" }, 0.5); tl.to(target, { opacity: 0, duration: 0.5, ease: "none" }, 2.5);

该模式在 T1 中已验证,平均 SSIM 0.974。实际语料 TitleCard.tsx 的源码正是interpolate(frame, [0, 15, 75, 90], [0, 1, 1, 0], { extrapolateLeft: "clamp", extrapolateRight: "clamp" })(淡入 0–15 帧、保持 15–75 帧、淡出 75–90 帧),其 HF 产物 index.html 中三条tl.to与上述代码逐条对应,duration恰好是相邻关键帧的帧距除以 30。

spring() 弹簧 → GSAP back.out

spring()损失最严重的翻译。Remotion 的弹簧基于物理模型(damping/stiffness/mass),而 GSAP 的back.out/elastic.out是参数化缓动,两者并非精确等价。但经验证,近似映射足以让真实合成保持 ≥ 0.92 SSIM。

Remotionspring配置GSAP 等价写法验证情况
{damping: 12, stiffness: 100, mass: 1}(干脆利落)back.out(1.4),时长约 0.7 sT2、T3(TitleScene)
{damping: 14, stiffness: 90, mass: 1}(更温和)back.out(1.2),时长约 0.7 sT3(StatCard)
{damping: 8, stiffness: 200}(非常有弹性)back.out(2.0)elastic.out(1, 0.5),时长约 0.6 s未验证;预算约 0.05 SSIM
{overshootClamping: true}power3.out,时长约 0.6 s(无过冲)未验证

经验法则back.out(N)的过冲比例 ≈(stiffness / damping^2) * 1.4。例如damping:12, stiffness:1001.4 * 100/144 ≈ 0.97,接近已验证的 1.4——公式只是粗略估计,最终以目视微调为准。典型配置的默认时长约 0.7 s。

当 spring 的delay/from/to参数非默认值时,按比例缩放时长(把延迟与位移折算进 tween 的偏移与目标值)。

源码佐证:T3 的 TitleScene.tsx 使用spring({ config: { damping: 12, stiffness: 100, mass: 1 } })驱动标题缩放,其 HF 产物 index.html 中对应tl.to(title, { scale: 1, duration: 0.7, ease: "back.out(1.4)" }, 0);StatCard 的{damping: 14, stiffness: 90, mass: 1}则对应back.out(1.2),两处注释都标注了映射来源。

interpolate + 自定义缓动 → GSAP 缓动等价表

Remotion 通过Easing传入自定义缓动:

import { Easing } from "remotion"; interpolate(frame, [0, 30], [0, 1], { easing: Easing.out(Easing.cubic) });

完整映射表:

RemotionGSAP
Easing.in(Easing.linear)ease: "none"
Easing.out(Easing.cubic)ease: "power3.out"
Easing.inOut(Easing.cubic)ease: "power3.inOut"
Easing.out(Easing.poly(N))ease: "power<N>.out"(N=2 对应 quad,3 对应 cubic,4 对应 quart,5 对应 quint)
Easing.bezier(a,b,c,d)CustomEase.create("c", "M0,0 C${a},${b} ${c},${d} 1,1")(需 CustomEase 插件)
Easing.elastic(bounciness)ease: "elastic.out(${bounciness}, 0.3)"
Easing.bounceease: "bounce.out"
Easing.back(overshoot)ease: "back.out(${overshoot * 1.7})"(Remotion 的过冲比例刻度不同,需放大 1.7 倍)

注意Easing.back的过冲刻度与 GSAP 不一致,这正是上面spring → back.out经验法则里"1.4 倍系数"的另一种体现形式。

interpolateColors 非数值属性 → GSAP 原生颜色动画

Remotion 用interpolateColors做颜色插值:

const color = interpolateColors(frame, [0, 30], ["#ff0000", "#0000ff"]);

GSAP 原生支持颜色 tween:

gsap.to(target, { color: "#0000ff", duration: 1.0, ease: "none" }, 0);

backgroundColorborderColor同理。from值由 CSS 或内联样式读取,无需在 JS 中重复声明。在 SKILL.md 的 lint 分级中,interpolateColors属于 Info 级(可直接翻译并附注),不会阻断移植。

自定义数字滚动 / count-up → 计数器对象 + onUpdate

当 Remotion 使用帧驱动的数字斜坡(Math.round(value * eased))时,典型源码:

const t = interpolate(frame, [0, 45], [0, 1]); const eased = 1 - (1 - t) ** 3; // cubic ease-out const value = Math.round(target * eased); return <div>{value.toLocaleString()}</div>;

GSAP 等价写法——tween 一个计数器对象,在onUpdate中写回textContent

const counter = { v: 0 }; tl.to( counter, { v: target, duration: 1.5, ease: "power3.out", onUpdate: () => { el.textContent = Math.round(counter.v).toLocaleString(); }, }, 0, );

要点:

  • power3.out1 - (1-t)^3精确相等,无需近似;
  • 在 T3 中验证,平均 SSIM 0.953;
  • 子帧时序偏移可能导致个别帧的数字显示略有出入,但终值必然收敛,对 SSIM 的影响低于噪声底,可忽略。

源码佐证:T3 的 AnimatedNumber.tsx 正是"45 帧内1 - (1-t)^3缓出 +toLocaleString"的实现,HF 产物 index.html 中三张 StatCard 各自对应一个counter对象 +tl.to(..., { duration: 1.5, ease: "power3.out", onUpdate }),起始偏移为场景起始时间加各自的 stagger。

Stagger 错峰 → 按实例 prop 换算时间线偏移

当自定义子组件接收delayInFramesprop 时:

<StatCard delayInFrames={i * 12} value={...} />

翻译为 GSAP 时间线偏移:

cards.forEach((card, i) => { const start = base + i * (12 / fps); // i * 0.4s at fps=30 tl.to(card, { ... }, start); });

在 T3 中验证:三张 StatCard 以 0.0 / 0.4 / 0.8 s 错峰入场(12 帧 × i / 30fps)。具体落地可对照 StatCard.tsx 的local = frame - delayInFrames局部帧逻辑——HF 端将这一"局部时间"折叠进全局时间线的偏移计算,i * 12帧直接换算为i * 0.4秒。

组合实践:从源码到 HF 时间线的完整链路

综合以上映射,一个典型的移植遵循 SKILL.md 的工作流:

  1. Lint 源:对 Remotion 源码跑 lint_source.py,拦截useState/useEffect等阻塞项(详见 escape-hatch.md),识别interpolate/spring/Easing/interpolateColors等需要本参考的构造;
  2. 规划映射:根据 api-map.md 确定需要加载的专题参考——出现useCurrentFrameinterpolatespringEasinginterpolateColors时加载本文对应的 timing.md;
  3. 生成 HF 合成:输出index.html,包含根#stagedata-composition-id/data-start/data-duration/data-fps等属性、扁平场景列表,以及一条暂停的 GSAP 时间线,所有useCurrentFrame()派生都变成时间线上正确偏移的 tween(参数化的 props 处理见 parameters.md);
  4. 验证:渲染两侧基线并做 SSIM 对比,阈值约为该复杂度层级 p05 之下 0.02(详见 eval.md)。

验证命令(参考 SKILL.md 的快速路径):

# 渲染 Remotion 基线 cd remotion-src && npx remotion render <CompositionId> out/baseline.mp4 # 渲染 HF 翻译 cd ../hf-src && npx hyperframes render --skill=remotion-to-hyperframes --output ../hf.mp4 # SSIM 对比 ../../scripts/render_diff.sh ./remotion-src/out/baseline.mp4 ./hf.mp4 ./diff

关键前提:两侧渲染必须使用匹配的像素格式,在 Remotion 源码的remotion.config.ts中设置Config.setVideoImageFormat("png")Config.setColorSpace("bt709"),否则 SSIM 差异度量的是编码器差异(约 0.05 SSIM 损失)而非翻译保真度。若对比失败,可用 frame_strip.sh 定位具体分歧帧,再回到本文对应的映射表复查。

小结:一份可执行的时序翻译清单

把 Remotion 的帧驱动动画翻译成 HyperFrames 时间线时,遵循以下核对项即可覆盖绝大多数场景:

  1. 单位统一:所有帧号除以fps换成秒,且只在翻译时换算一次;
  2. 线性插值:默认ease: "none",注意extrapolateRight: "clamp"与 GSAP 天然一致,"extend"需手动外推输入范围;
  3. 多段插值:拆成多条时间线 tween,偏移取各关键帧对应秒数;
  4. 弹簧:按 spring → back.out 映射表 选取back.out参数与时长,未验证组合预留约 0.05 SSIM 的偏差预算,最终目视微调;
  5. 自定义缓动:对照 缓动等价表,back过冲乘 1.7,bezier需 CustomEase 插件;
  6. 颜色/数字:颜色用 GSAP 原生 tween;数字滚动用计数器对象 +onUpdatetextContentpower3.out精确对应 cubic ease-out;
  7. staggerdelayInFrames除以fps变为时间线偏移,叠加在场景基础时间之上。

所有映射均已通过 T1–T3 测试语料 的 SSIM 阈值验证,可直接作为移植 Remotion 合成到 HyperFrames 的实战参考。

【免费下载链接】hyperframesWrite HTML. Render video. Built for agents.项目地址: https://gitcode.com/GitHub_Trending/hy/hyperframes

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询