最近在用 pi 跑一个小项目的时候,发现 HyperFrames 这个新框架——它能把浏览器里的 Lottie 动画原样渲染成 1080p 的视频。我当时就有个想法:能不能用 pi 写代码,搭一套 Lottie + HyperFrames 的演示视频,把"为什么 Lottie 适合做技术演示"这件事讲明白?
于是就有了 C:/Users/Administrator/lottie-video/ 这个项目。视频只有 18 秒,4 个分镜,2.6 MB,0 lint 错误。过程里踩了一堆坑,今天把完整流程和血泪教训都写下来。
在动手前先明确分工,省得后面搞混。
pi 是编码 Agent。我用 pi -c "..." 让它帮我把 4 个分镜的 HTML 写出来,同时在踩坑时直接问它"这个报错是什么",省了大量查文档的时间。
HyperFrames 是一个视频生成框架,它的核心思路是:把浏览器里跑的东西(HTML + CSS + Lottie)当作"分镜",用 Puppeteer 逐帧截图,再 ffmpeg 拼成视频。和 Remotion 比,它的优势是完全兼容 lottie-web——你写好 Lottie 加载代码,框架会自动捕获每一帧。
Lottie 是 Airbnb 开源的 JSON 动画格式,体积小、矢量、可时间寻址。这次我从 LottieFiles 公开 CDN 下了 3 个动画:
| 文件 | 用途 | 大小 |
|---|---|---|
assets/checkmark.json |
庆祝纸屑(confetti) | 614 KB |
assets/loading.json |
浮动粒子 | 8 KB |
assets/rocket.json |
火箭发射 | 2 KB |
整个项目只有 8 个文件,目录非常干净:
lottie-video/
├── index.html # 主时间线(18 秒)
├── hyperframes.json # 框架配置
├── meta.json # 项目元信息
├── package.json # 依赖 hyperframes 0.6.56
├── assets/
│ ├── checkmark.json # 614 KB confetti
│ ├── loading.json # 8 KB
│ └── rocket.json # 2 KB
├── compositions/
│ ├── scene1-rocket-launch.html # 0-4s
│ ├── scene2-features.html # 4-10s
│ ├── scene3-confetti-celebration.html # 10-14s
│ └── scene4-outro.html # 14-18s
└── lottie-demo.mp4 # 最终输出 2.6 MB
4 个分镜用 <div class="clip"> 包裹,每个 clip 显式声明 4 个 data-* 属性:开始时间、结束时间、HTML 源、音频。index.html 里的 GSAP 主时间线只在 seek 到对应时间点时调用 lottie.goToAndStop() 来精确控制每个动画的当前帧。
Scene 1 火箭发射(0-4 秒):开场要冲击力。一颗紫粉渐变的火箭从画面下方飞升,背景是深紫到深蓝的径向渐变。底部中文字幕"用 After Effects 动画,在浏览器里渲染成视频",提前预告核心技术。
Scene 2 三大特性(4-10 秒):3 张并排卡片,每张卡片顶部是"VECTOR/LIGHTWEIGHT/SEEKABLE"双语标签,中间是紫粉渐变的 stat 数字(∞ / 10× / 100%),下方是 3 行 feature-item 文字。卡片中央各有一个旋转光环 logo——外环顺时针 4 秒一圈,内环逆时针 3 秒一圈,核心球 1.6 秒呼吸一次。
Scene 3 庆祝纸屑(10-14 秒):confetti 满屏飘落 + 4 角 sparkle + 8 颗彩色粒子向 8 个方向飞出 + 大冲击波 shockwave + 中心 burst Lottie + 旋转光环 + "READY TO SHIP" 徽章 + "立即发布" 4 字居中。这一帧信息密度最高,所以文字背板用 4 层 text-shadow 强化可读性。
Scene 4 终端打字机(14-18 秒):黑色终端外壳,三行代码用 GSAP 拆字符逐字打出——npx --yes hyperframes@0.6.56 第 1 行,render --output lottie-demo.mp4 第 2 行,--quality draft --fps 30 第 3 行。每打出一行后,紫色光标闪 2 次(yoyo: true, repeat: 1),然后下一行开始。
这一步是整个项目最关键的技术点,写错了就白干。
核心模式:
const anim = lottie.loadAnimation({
container: document.getElementById("xxx"),
renderer: "svg",
loop: false, // 或 true(lint 允许)
autoplay: false, // 关键:必须 false,由 GSAP 控制
path: "../assets/xxx.json",
});
window.__hfLottie = window.__hfLottie || [];
window.__hfLottie.push(anim);
3 个必填规范:
autoplay: false — 必须关掉自动播放,由 GSAP 主时间线统一控制。如果开 autoplay,每个 Lottie 会在加载时自己跑,到渲染时已经播完了
loop: false(一般情况)— 一次性动画设为 false,循环动画(如 Scene 3 中心 burst)可以设 true。GSAP 会在 seek 到对应时间点时调用 goToAndStop(frame, true) 跳转到指定帧
注册到 window.__hfLottie — HyperFrames 在渲染时需要枚举所有 Lottie 实例来 seek。漏注册 = 渲染时 Lottie 卡在第 0 帧
主时间线挂在 window.__timelines,必须 paused: true:
const tl = gsap.timeline({ paused: true });
window.__timelines = window.__timelines || [];
window.__timelines.push(tl);
Scene 4 打字机实现(每个字符包 <span class="ch">,空格替换为 ):
const text = "npx --yes hyperframes@0.6.56";
const container = document.querySelector(".typed-text");
container.innerHTML = text.split("").map(c =>
c === " " ? '<span class="ch"> </span>' : `<span class="ch">${c}</span>`
).join("");
tl.to(".typed-text .ch", {
opacity: 1,
duration: 0.04,
stagger: 0.05 // 每个字符间隔 0.05 秒
}, 14.0); // 14 秒开始打字
打字完成后,光标闪 2 次(0.4 秒一闪):
tl.to(".cursor", { opacity: 0, duration: 0.4, yoyo: true, repeat: 1 }, 16.5);
这部分是这篇最有价值的部分,建议认真看。
坑 1:GSAP rotation 对 border 圆环不触发
我把卡片中央的 logo 设计成"双环 + 核心球",外环用 border 描边模拟旋转圆弧。但 GSAP 的 tl.to(".ring", { rotation: 360 }) 完全不生效——双环静止不动。
排查后发现:GSAP 的 rotation 走 transform matrix 矩阵,但 border-top-color + border-right-color + border-radius: 50% 这种"半圆弧"在某些渲染路径下 transform 不触发合成层。
解决:放弃 GSAP,改用 CSS @keyframes:
@keyframes spin-cw {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.middle-logo-ring {
animation: spin-cw 4s linear infinite;
transform-origin: 50% 50%; /* 显式声明,Skia 渲染更稳 */
}
坑 2:GSAP repeat: -1 触发 lint 错误
HyperFrames 的 lint 规则会拒绝 repeat: -1(无限循环),报错 gsap_infinite_repeat。
解决:用有限计数公式:
// 视频总时长 18 秒,循环周期 4 秒 → repeat 3 次
const duration = 18;
const cycleDuration = 4;
tl.to(".ring", { rotation: 360, duration: cycleDuration,
repeat: Math.floor(duration / cycleDuration) - 1 });
// 注意:yoyo 时 cycleDuration 翻倍
坑 3:backdrop-filter: blur(28px) 让文字模糊
Scene 3 我想给"立即发布"做一个磨砂玻璃背板,于是加了 backdrop-filter: blur(28px)。结果文字在模糊层上面显得发虚,渲染出来像近视眼没戴眼镜。
解决:去掉 backdrop-filter,改用纯色渐变背板 + 多层 text-shadow 模拟光晕:
.center-content {
background: linear-gradient(135deg,
rgba(10, 10, 30, 0.92) 0%,
rgba(20, 20, 50, 0.88) 50%,
rgba(10, 10, 30, 0.85) 100%);
/* 不要 backdrop-filter */
}
.big-text {
text-shadow:
0 0 24px rgba(139, 92, 246, 1),
0 0 48px rgba(139, 92, 246, 0.8),
0 0 80px rgba(236, 72, 153, 0.7),
0 4px 16px rgba(0, 0, 0, 0.9);
}
坑 4:background-clip: text 在 Skia 渲染不稳定
Scene 3 想给"立即发布"做紫粉橙三色渐变文字,用了 background-clip: text。在 Chrome 里看着正常,但 HyperFrames 用 Puppeteer 渲染出来时文字要么透明要么颜色错乱。
解决:放弃 gradient 文字,改用纯白 + 6 层 text-shadow(紫光晕 + 粉光晕 + 橙光晕 + 黑色硬投影)。视觉效果比 gradient 更稳。
坑 5:元素层级遮挡
Scene 3 信息密度高,confetti 背景、8 颗彩色粒子、旋转光环、burst Lottie 都在 0-3 层之间。我一开始把"立即发布"文字也放在 z-index: 3,结果被粒子和光环挡住。
解决:强制新建 stacking context + 提高 z-index:
.center-content {
z-index: 100; /* 从 3 提到 100 */
isolation: isolate; /* 强制新建 stacking context */
}
背板用 4 层:径向渐变(0.78→0.15 透明度)+ 28px blur + 80px 紫光 + 60px 黑色阴影。
坑 6:npm cache 锁冲突
npx --yes hyperframes@0.6.56 render 报 EEXIST 错。
解决:
rm -rf C:/Users/Administrator/AppData/Local/npm-cache/_npx/
坑 7:字体警告 font_family_without_font_face
Lint 提示我用了 "PingFang SC", "Microsoft YaHei" 但没加 @font-face。
解决:直接移除这行中文字体声明,让浏览器回退到系统默认字体(宋体/微软雅黑)。HyperFrames 渲染时用的是真实浏览器,中文字体不会丢。
坑 8:音频末尾 2 秒静默
我的背景音乐只有 16 秒,视频是 18 秒。最后 2 秒没声音,听起来很突兀。
解决:两种方案——
- 方案 A:用 FFmpeg 延长音频(ffmpeg -i in.mp3 -filter:a "apad=pad_dur=2" out.mp3)
- 方案 B:<audio> 标签加 loop="true",但会有 1 秒断点
- 我选了方案 C:把视频缩到 16 秒,或者在最后 2 秒加个静音但有字幕的过渡
最后我把视频时长和音频对齐了。
整个项目最关键的一行命令:
npx --yes hyperframes@0.6.56 render \
--output lottie-demo.mp4 \
--quality draft \
--fps 30
--quality draft 渲染快(2 分 52 秒),--quality production 慢 4 倍但质量更高。发布前我一般先 draft 跑一遍看效果,确认后再 production 一次。
--fps 30 是默认值。如果动画有高速运动(比如粒子爆炸),可以加到 60,但文件大小会翻倍。
完整流程:
# 1. lint 检查
npx --yes hyperframes@0.6.56 lint
# 2. 渲染(先 draft 验证)
npx --yes hyperframes@0.6.56 render --output lottie-demo.mp4 --quality draft --fps 30
# 3. 抽帧验证
ffprobe lottie-demo.mp4
ffmpeg -i lottie-demo.mp4 -vf "select='eq(n,90)+eq(n,180)+eq(n,270)'" -vsync 0 frames-%02d.png
# 4. 生产级渲染
npx --yes hyperframes@0.6.56 render --output lottie-demo.mp4 --quality production --fps 30
最终视频:
| 指标 | 数据 |
|---|---|
| 文件大小 | 2.6 MB |
| 时长 | 18.02 秒 |
| 分辨率 | 1920×1080 |
| 帧率 | 30 fps |
| 视频编码 | h264 |
| 音频 | aac 48 kHz 立体声 |
| Lottie 总数 | 29 个动画实例 |
| Lint | 0 errors / 0 warnings |
| 渲染时间(draft) | 2 分 52 秒 |
| 渲染时间(production) | 约 11 分钟 |
29 个 Lottie 同时跑 18 秒,2.6 MB 压缩比非常高——比用 After Effects 导出再转码的 MP4 小一个数量级。
这套方案不只适合做 Lottie 演示视频,常见的扩展场景:
| 场景 | 改动 |
|---|---|
| 产品宣传片 | 把 Lottie 换成产品截图 + GSAP 入场动画 |
| 教学录屏 | 在主时间线加 <video> 标签嵌入录屏文件 |
| 数据可视化 | 用 GSAP + SVG 画图表,每秒渲染一帧 |
| 字幕视频 | <audio> + GSAP 时间线同步字幕条 |
本质上,HyperFrames 把"浏览器 = 视频编辑器"这件事做实了。HTML/CSS/Lottie 写什么,渲染出来就是什么,没有中间格式损耗。
把脑洞变成 18 秒视频,传统流程要 After Effects + 视频剪辑软件 + 渲染农场。这套方案用 8 个 HTML 文件 + 1 个 npm 命令就能跑出 1080p 高清视频,整个过程代码可版本控制、可复用、可改参数即时预览。
Lottie + HyperFrames 这套组合最大的价值是动画资产可复用——同一个 checkmark.json 既能嵌在网页里做交互效果,又能直接渲染进视频做开场动画。设计师做一次,开发用两次,效率翻倍。
你用 Lottie 做过最复杂的动画是什么?有没有想过把它直接渲染成视频?评论区聊聊你的脑洞。
💬 评论区