我用 HyperFrames 做了个 Pi Agent 的 20 秒介绍视频。
20 秒,5 个分镜,2.4MB MP4,1920×1080 24fps。
听起来简单,实际过程中踩了 6 个值得记下来的坑。今天写一篇复盘,给同样想用 HyperFrames 的人当避雷针。
视频结构:
| 分镜 | 时长 | 内容 |
|---|---|---|
| Scene 1 | 0-2s | π Logo 开场 + glow 动画 |
| Scene 2 | 2-6s | 「Pi 是什么」标题 + 简介文字 |
| Scene 3 | 6-11s | 6 个核心能力卡片 |
| Scene 4 | 11-16s | Skills + Tools 强调 |
| Scene 5 | 16-20s | 收尾 + CTA |
最终文件:pi-agent-intro.mp4,2,392,325 字节,1920×1080,20 秒整。
整个项目用 5 个 HTML + 1 个 index.html + 1 段背景音乐就完成了。HTML 即视频,浏览器即编辑器。
pi-agent-video/
├── index.html # 主时间线(root timeline)
├── meta.json # 项目元信息
├── hyperframes.json # 框架配置
├── package.json # npm scripts
├── pi-agent-intro.mp4 # 最终产物
├── compositions/
│ ├── scene1-logo-intro.html
│ ├── scene2-what-is-pi.html
│ ├── scene3-core-features.html
│ ├── scene4-skills-tools.html
│ └── scene5-outro.html
└── assets/
└── (预留)
HyperFrames 的核心思想:每个分镜是一个独立的 HTML 文件,根 index.html 用 data-composition-src 引用它们。每个分镜内部用 gsap.timeline() 控制自己的动画。
window.__timelines 没注册 → 渲染时画面静止症状:本地浏览器预览动画正常,npm run render 出来的 MP4 画面定格在初始状态。
原因:GSAP timeline 必须注册到 window.__timelines 上,HyperFrames 渲染时通过这个对象控制时间轴。
正确写法:
(function () {
const tl = gsap.timeline({ paused: true });
// ... 配置动画
window.__timelines = window.__timelines || {};
window.__timelines["scene1-logo-intro"] = tl;
})();
关键点:
1. timeline 必须是 paused: true——HyperFrames 自己控制播放
2. 用 IIFE 包裹避免全局污染
3. 键名要和分镜 ID 一致(HTML 里的 data-composition-id)
我第一次漏掉了 paused: true,结果 GSAP 在脚本执行时就开始跑,渲染时画面已经走完了。
class="clip" → 元素永久可见症状:分镜该出现时出现了,但不该出现时也显示——下一个分镜开始后,上一个分镜的元素还残留。
原因:HyperFrames 用 class="clip" 来切换元素的可见性。没加这个 class 的元素不受时间轴控制,永久显示。
正确写法:
<div data-composition-id="scene1-logo-intro"
data-start="0"
data-duration="2"
data-track-index="1">
<div class="clip">
<!-- 实际内容 -->
</div>
</div>
经验:所有带 data-start 和 data-duration 的可见元素,必须包一层 class="clip"。这是个隐形规范,不读文档真不知道。
data-* 属性缺一不可症状:npm run check 报错,渲染失败。
原因:每个分镜 div 必须同时有 4 个属性:
data-composition-id="scene1-logo-intro" <!-- 唯一标识 -->
data-start="0" <!-- 开始时间(秒)-->
data-duration="2" <!-- 持续时间(秒)-->
data-track-index="1" <!-- 轨道索引 -->
缺一个就报错。最常漏的是 data-track-index——以为是可选项,其实渲染时需要用它来布局时间轴。
症状:场景切换时画面有黑帧。
原因:分镜 1 是 0-2s,分镜 2 写成了 2.1-6s,中间 0.1 秒是黑的。
正确接法:
<!-- 分镜 1: 0-2s -->
<div data-composition-src="scene1" data-start="0" data-duration="2"></div>
<!-- 分镜 2: 2-6s(紧贴)-->
<div data-composition-src="scene2" data-start="2" data-duration="4"></div>
<!-- 分镜 3: 6-11s -->
<div data-composition-src="scene3" data-start="6" data-duration="5"></div>
经验:分镜 2 的 data-start 必须等于分镜 1 的 data-start + data-duration,以此类推。写个表格维护时间轴,比目测可靠。
症状:本地预览听不到音乐,渲染出来 MP4 是哑的。
原因:
<audio id="bg-music" src="E:/hermes-agent/share-video/static/music.mp3" ...></audio>
根问题:用了绝对路径 E:/hermes-agent/...,换台机器就失效;而且这个路径根本不在当前项目里。
正确做法:
# 把音乐拷到项目里
cp /path/to/music.mp3 assets/bgm.mp3
<audio id="bg-music" src="assets/bgm.mp3" data-volume="0.3" ...></audio>
经验:HyperFrames 渲染时会用 Chromium headless,路径必须相对项目根目录。./ 或 assets/...,别用绝对路径。
npm run dev 看似卡住 → 服务被 kill症状:第一次跑 npm run dev,命令行一直「没反应」,我以为挂了,按 Ctrl+C 重启,结果浏览器报 404。
原因:npm run dev 是个长驻 HTTP 服务(类似 vite dev),不是一次性命令。
正确用法:
# 后台运行(绝对不要前台跑)
nohup npm run dev > dev.log 2>&1 &
# 等服务起来
sleep 3
# 浏览器打开 http://localhost:5173
经验:
- npm run dev 启动后不会退出——这是正常的
- 想停服:pkill -f hyperframes 或 kill <PID>
- 在 Claude Code 里用 run_in_background: true
- 想跑命令就 npm run check / npm run render,这两个是一次性命令
最终输出 MP4:
npm run render
等 1-2 分钟(取决于分镜数和分辨率),产物在项目根目录。
我的实际数据:
- 5 个分镜,20 秒
- 1920×1080
- 24fps
- 渲染耗时:约 90 秒
- 文件大小:2.39 MB
- 音频:h264 + aac
比用 After Effects 之类的工具快 5-10 倍——主要是改 HTML 不用重渲染全片。
npm run check——lint 能抓出 90% 的低级错误./assets/bgm.mp3 而不是 C:/... 或 E:/...window.__timelines 注册放最后——timeline 配置完才注册,防止未定义就触发<audio>——不要用 <video> 兼任,视频和音频轨道分开控制npm run dev 永远后台跑——前台跑会被 Claude Code 的 timeout kill 掉HyperFrames 的最大价值不是「快」——是可版本化、可协作。
整个项目是 6 个 HTML 文件,丢 Git 就能协作:
- 美工改动画 → 改 HTML,PR 即可
- 文案改文字 → 改 HTML,PR 即可
- 翻译多语言 → 复制 HTML 改文案,PR 即可
不像 After Effects 的 .aep 文件——传不了 Git,也改不动 diff。
20 秒视频背后的成本:
| 维度 | 数字 |
|---|---|
| 实际开发时间 | 约 2 小时(含踩坑) |
| HTML 文件 | 6 个 |
| 总代码行数 | 约 250 行 |
| 渲染时间 | 90 秒 |
| 文件大小 | 2.39 MB |
如果你也在做「网页内容视频化」或「产品介绍视频」,HyperFrames 值得一试。
💬 评论区