用 HTML+GSAP 制作专业视频:HyperFrames 深度实践指南
1. 用 HTML+GSAP 制作专业视频:HyperFrames 深度实践指南
本文整理自一次完整的 HyperFrames 实战记录,涵盖框架原理、安装配置、视频制作全流程,以及背景音乐、背景视频的引入方法,并附上真实踩坑经验。
1.1. HyperFrames 是什么
HyperFrames 是一个以 HTML 为核心的视频合成框架。
它的核心理念是:HTML 是视频的 source of truth(唯一真相来源)。
传统视频制作工具(如 Premiere、After Effects)使用专有格式和二进制文件描述动画;HyperFrames 把这个过程翻转过来——用普通的 HTML 文件、CSS 样式和 GSAP 动画库来描述视频内容,再通过 CLI 工具把它渲染成 MP4。
一句话概括:HyperFrames = HTML/CSS/GSAP 写动画 → CLI 工具截帧 → 编码输出 MP4。
developer writes HyperFrames CLI output
index.html → render engine → video.mp4
(HTML + GSAP) (headless browser + frame capture)
1.1.1. 核心组件
| 组件 | 说明 |
|---|---|
| Composition | 一个 HTML 文件,通过 data-* 属性描述时序 |
| Timeline | GSAP timeline,控制所有动画,必须设置 { paused: true } |
| Clip | 视频、音频、图片、div 元素,通过 data-start/data-duration 定义时间点 |
| CLI | npx hyperframes render / lint / validate |
1.2. 底层渲染原理
理解渲染原理是避免踩坑的关键。
HyperFrames 的渲染流程分为三个阶段:
1.2.1. 阶段一:编译(Compile)
index.html
│
├─ 字体嵌入:从 Google Fonts 获取字体,转为 base64 @font-face 内联进 HTML
├─ CDN 脚本内联:把外部 JS(如 GSAP CDN)下载并内联,确保离线可用
├─ 元数据提取:读取 data-duration、media 数量等
└─ 输出:编译后的 HTML(自包含,不依赖网络)
1.2.2. 阶段二:帧捕获(Frame Capture)
编译后的 HTML
│
├─ 启动无头浏览器(Headless Chrome)
├─ 加载 HTML,GSAP timeline 自动初始化(paused 状态)
├─ 框架调用 timeline.seek(t) 逐帧定位
│ └─ 默认 30fps → 每隔 1/30s 截一帧
├─ 对每帧截图(Screenshot)
└─ 多 Worker 并行处理(默认按 CPU 核心数自动配置)
1.2.3. 阶段三:编码(Encode)
PNG 帧序列 + 音频轨道
│
└─ FFmpeg 编码 → MP4 (H.264 + AAC)
1.2.4. 关键约束(由渲染原理决定)
- Timeline 必须是确定性的:
seek(t)要求每次跳到同一时间点结果相同,因此禁止使用Math.random()、Date.now()。 - Timeline 必须同步构建:渲染引擎在页面加载后同步读取
window.__timelines,禁止在async/setTimeout/Promise内构建。 - 禁止
repeat: -1:无限循环无法被seek()正确定位,必须用Math.ceil(duration / cycle) - 1计算有限次数。 - 媒体元素不可手动控制:框架接管所有
video.play()/audio.play(),开发者不得调用。
1.3. 优点与缺点
1.3.1. ✅ 优点
1.3.1.1. 技术栈亲和力强
前端开发者几乎零学习成本。
HTML + CSS + JS,没有任何专有 DSL,所有 CSS 动画效果、GSAP 缓动函数直接可用。
1.3.1.2. 版本可控
视频源文件就是 HTML,可以 git commit、diff、review,像代码一样管理视频资产。
1.3.1.3. 字体自动嵌入
只需在 CSS 里写 font-family: "Bricolage Grotesque",编译器自动从 Google Fonts 拉取并以 base64 内联,渲染机器无需安装字体。
1.3.1.4. 强大的动画能力
GSAP 是业界最成熟的 JS 动画库,支持复杂缓动、交错动画(stagger)、时间线嵌套。
1.3.1.5. 子合成(Sub-composition)
可以把长视频拆分为多个独立的 HTML 文件,通过 data-composition-src 组合,便于模块化管理。
1.3.1.6. Lint + Validate 工具
内置 hyperframes lint(检查代码规范)和 hyperframes validate(WCAG 色彩对比度审计),输出视频前可自动发现问题。
1.3.1.7. 无时长限制
理论上支持任意时长,实际限制是渲染时间和内存。
1.3.2. ❌ 缺点
1.3.2.1. 视频素材需要预处理
原始录屏(如 iOS Simulator 录制)通常是 VFR(可变帧率)+ 稀疏关键帧,直接使用会导致渲染卡顿、帧冻结。
必须用 FFmpeg 预处理:
ffmpeg -i input.mp4 -c:v libx264 -r 30 -g 30 -keyint_min 30 -movflags +faststart output.mp4
1.3.2.2. CSS transform 与 GSAP 冲突
如果 CSS 里有 transform: translateY(-50%) 用于居中,同时 GSAP 又对同一元素的 y 做动画,GSAP 会完全覆盖 CSS transform,导致居中失效。
解决方法:把 CSS transform 换成 GSAP 的 xPercent/yPercent。
1.3.2.3. 浏览器预览与渲染行为不一致
- 浏览器自动播放策略:音频必须等用户手势后才能播放,渲染时没有此限制。
- 需要维护两套逻辑(
if (!window.__hyperframes)分支)处理预览时的媒体播放。
1.3.2.4. 本地文件 CORS 限制
浏览器直接打开 file:// 协议下的 HTML 时,带 crossorigin="anonymous" 的本地资源会被 CORS 拦截。
本地预览时需去掉 crossorigin 属性,或用本地 HTTP 服务器。
1.3.2.5. 外部图片资源不可控
使用 Pollinations.ai 等在线图片 API 时,渲染机器需要网络访问,且图片生成结果可能变化(建议提前下载到本地)。
1.3.2.6. 渲染速度较慢
30fps 视频,72 秒 = 2160 帧,每帧需截图编码,总渲染时间通常是视频时长的数倍。
1.4. 适合的使用场景
| 场景 | 适合度 | 说明 |
|---|---|---|
| App 产品介绍视频 | ⭐⭐⭐⭐⭐ | 文字动效、手机 mockup、数据展示,HTML/CSS 天然擅长 |
| 营销短视频 | ⭐⭐⭐⭐⭐ | 品牌色、排版、转场效果完全可控 |
| 数据可视化视频 | ⭐⭐⭐⭐⭐ | 配合 Charts.js / D3,动态图表天然支持 |
| 教学/解说视频 | ⭐⭐⭐⭐ | 古风水墨、字幕、注释,排版能力强 |
| 社交媒体内容 | ⭐⭐⭐⭐ | 支持 1080×1920 竖屏格式 |
| 真人出镜视频 | ⭐ | 无法处理实时摄像头,需配合视频素材 |
| 3D 渲染场景 | ⭐⭐ | 仅支持 CSS 3D 变换,无法调用 WebGL 渲染器 |
1.5. 安装与环境配置
1.5.1. 前提条件
- Node.js >= 18(推荐使用 nvm 管理)
- FFmpeg(视频预处理必需)
# 安装 Node.js(推荐 nvm)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
nvm install 20
nvm use 20
# 安装 FFmpeg(macOS)
brew install ffmpeg
1.5.2. 使用 npx(无需全局安装)
HyperFrames 推荐通过 npx 使用,无需全局安装:
# 渲染视频(传入目录路径,不是文件路径)
npx hyperframes render <目录> --output <输出路径>
# 代码检查
npx hyperframes lint
# 对比度审计
npx hyperframes validate
⚠️ 注意:
render命令传入的是目录路径,不是文件路径。
1.5.3. 目录结构
my-video/
├── index.html ← 合成文件(必须)
├── video.mp4 ← 应用录屏(可选)
├── music.mp3 ← 背景音乐(可选)
└── images/ ← 图片资源(可选)
1.6. 制作第一个视频:完整步骤
1.6.1. 基础模板
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
width: 1920px;
height: 1080px;
overflow: hidden;
background: #1A1A2E;
font-family: 'Bricolage Grotesque', sans-serif;
}
.scene {
position: absolute;
top: 0; left: 0;
width: 1920px; height: 1080px;
overflow: hidden;
}
#scene1 { z-index: 1; }
#scene2 { z-index: 2; opacity: 0; } /* 非首场景默认隐藏 */
</style>
</head>
<body>
<!-- data-duration:视频总时长(秒) -->
<div
id="root"
data-composition-id="main"
data-width="1920"
data-height="1080"
data-start="0"
data-duration="20"
>
<div id="scene1" class="scene">
<h1 id="title">Hello, HyperFrames</h1>
</div>
<div id="scene2" class="scene">
<h2 id="subtitle">制作你的第一个视频</h2>
</div>
</div>
<script>
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true }); // ← 必须 paused
// 场景 1 动画
tl.from("#title", { opacity: 0, y: 40, duration: 1.0, ease: "power3.out" }, 0.5);
// 转场:场景 1 → 场景 2(T=8s)
var T1 = 8;
tl.to("#scene1", { opacity: 0, duration: 0.6, ease: "power2.inOut" }, T1);
tl.set("#scene1", { visibility: "hidden" }, T1 + 0.61); // ← 防残影
tl.fromTo("#scene2",
{ opacity: 0 },
{ opacity: 1, duration: 0.6, ease: "power2.inOut" },
T1 + 0.2
);
// 场景 2 动画
tl.from("#subtitle", { opacity: 0, x: -30, duration: 0.8, ease: "expo.out" }, T1 + 0.8);
// 注册 timeline(必须)
window.__timelines["main"] = tl;
// 浏览器预览模式:自动播放
if (!window.__hyperframes) {
tl.play();
}
</script>
</body>
</html>
1.6.2. 渲染命令
# 在项目根目录执行
npx hyperframes render my-video --output my-video/output.mp4
1.7. 引入背景音乐
HyperFrames 规定音频必须使用独立的 <audio> 元素(禁止用视频元素的音轨),通过 data-* 属性与时间线同步。
1.7.1. HTML 结构
<!-- bg-music:背景音乐,data-start="0" 表示从第 0 秒开始播放 -->
<div id="root" data-composition-id="main" data-duration="34" ...>
<audio
id="bg-music"
data-start="0"
data-duration="34"
data-track-index="0"
src="music.mp3"
data-volume="0.55"
></audio>
</div>
1.7.2. 浏览器预览时的音频问题
浏览器安全策略禁止自动播放音频,需要用户手势触发。
解决方案:
if (!window.__hyperframes) {
// 创建点击播放按钮,覆盖整个画面
var btn = document.createElement("div");
btn.innerHTML = "▶ 点击播放";
btn.style.cssText = [
"position:fixed", "inset:0", "display:flex",
"align-items:center", "justify-content:center",
"font-size:40px", "color:rgba(255,255,255,0.8)",
"background:rgba(0,0,0,0.7)", "cursor:pointer",
"z-index:9999"
].join(";");
document.body.appendChild(btn);
btn.addEventListener("click", function() {
btn.remove();
document.getElementById("bg-music").play().catch(function(){});
tl.play();
});
}
1.7.3. 本地文件注意事项
- 不要加
crossorigin="anonymous":本地file://协议下 CORS 检查会失败,导致音频加载 404。 - 文件格式推荐 MP3 或 AAC,兼容性最好。
1.8. 引入背景视频
1.8.1. HTML 结构
<!-- app-video:视频轨道(静音,画面用),在时间线第 6 秒开始播放 -->
<div id="root" data-composition-id="main" data-duration="34" ...>
<video
id="app-video"
data-start="6"
data-duration="25"
data-track-index="1"
src="video.mp4"
muted
playsinline
style="display:block; width:100%; height:100%; object-fit:cover;"
></video>
<!-- 音频轨道(单独提取音频) -->
<audio
id="app-audio"
data-start="6"
data-duration="25"
data-track-index="2"
src="video.mp4"
data-volume="1"
></audio>
</div>
⚠️ 相同的 clip 不能在时间上重叠,但不影响视觉层级(层级用 CSS 控制)。
1.8.2. 视频预处理(重要!)
iOS 模拟器录屏、手机录制的视频通常是 VFR(可变帧率),且关键帧间距过大,直接用于 HyperFrames 会导致:
- 画面冻结
- 帧跳跃
- seek 失败
必须用 FFmpeg 预处理:
# -r 30:固定 30fps
# -g 30:每 30 帧一个关键帧(1 秒一个)
# -keyint_min 30:最小关键帧间距
ffmpeg -i input.mp4 \
-c:v libx264 \
-r 30 \
-g 30 \
-keyint_min 30 \
-movflags +faststart \
-c:a copy \
output-fixed.mp4
1.8.3. 浏览器预览时的视频播放
渲染时框架自动控制视频播放,浏览器预览时需要手动触发:
var appVid = document.getElementById("app-video");
// 视频加载失败时显示占位符
appVid.addEventListener("error", function() {
document.getElementById("placeholder").style.display = "flex";
});
if (!window.__hyperframes) {
// 在 timeline 到达视频开始时间时触发播放
tl.call(function() {
appVid.play().catch(function(){});
}, [], 6); // 第 6 秒触发
}
1.9. 多场景切换与转场
1.9.1. 规则(非常重要)
- 必须有转场:禁止直接跳切(jump cut)
- 每个场景必须有入场动画:所有元素通过
gsap.from()动画进入,禁止直接出现 - 仅最后一个场景可以有退场动画:其他场景的"退出"由转场效果承担
- 转场后必须添加 visibility 硬关闭:防止旧场景残影
// ✅ 正确写法
var T1 = 8; // 转场时间点
// 场景退出
tl.to("#scene1", { opacity: 0, duration: 0.6 }, T1);
tl.set("#scene1", { visibility: "hidden" }, T1 + 0.61); // 硬关闭
// 场景进入
tl.fromTo("#scene2",
{ opacity: 0 },
{ opacity: 1, duration: 0.6 },
T1 + 0.2
);
// 场景 2 入场动画(必须有)
var S2 = T1 + 0.8;
tl.from("#s2-title", { y: 50, opacity: 0, duration: 0.8, ease: "power3.out" }, S2);
tl.from("#s2-subtitle", { y: 30, opacity: 0, duration: 0.6, ease: "power2.out" }, S2 + 0.3);
1.9.2. 常见转场类型
// 淡入淡出(最简单)
tl.to("#scene1", { opacity: 0, duration: 0.5 }, T);
tl.fromTo("#scene2", { opacity: 0 }, { opacity: 1, duration: 0.5 }, T + 0.1);
// 模糊溶解(电影感)
tl.to("#scene1", { filter: "blur(20px)", opacity: 0, duration: 0.7 }, T);
tl.fromTo("#scene2",
{ filter: "blur(20px)", opacity: 0 },
{ filter: "blur(0px)", opacity: 1, duration: 0.7 },
T + 0.2
);
// 缩放溶解(有力量感)
tl.to("#scene1", { scale: 1.05, opacity: 0, duration: 0.8 }, T);
tl.fromTo("#scene2",
{ scale: 0.95, opacity: 0 },
{ scale: 1, opacity: 1, duration: 0.8 },
T + 0.2
);
1.10. 常见错误与修复
1.10.1. 浏览器打开一片黑
原因:GSAP from() 在 timeline 创建时立即设置 start state(opacity: 0),而 timeline 是 paused 的,所以画面停在所有元素都不可见的状态。
修复:
if (!window.__hyperframes) {
tl.play(); // 浏览器预览时自动播放
}
1.10.2. 手机/元素位置偏移
原因:CSS 使用了 transform: translateY(-50%) 进行垂直居中,GSAP 对同元素的 y 做动画时会完全覆盖 CSS transform,居中失效。
修复:
/* ❌ 错误:依赖 CSS transform 居中 */
#phone-wrap {
top: 50%;
transform: translateY(-50%); /* 会被 GSAP 覆盖 */
}
// ✅ 正确:用 GSAP 的 yPercent 代替 CSS transform
tl.set("#phone-wrap", { yPercent: -50 }, 0);
// 入场动画保持 yPercent
tl.fromTo("#phone-wrap",
{ yPercent: -50, y: 160, opacity: 0 },
{ yPercent: -50, y: 0, opacity: 1, duration: 1.2 },
S2 + 0.35
);
1.10.3. 视频只播几秒就消失
原因:data-duration 设置过短,场景在视频播完之前就结束了。
修复:检查 data-duration 是否覆盖了完整的视频播放时间,并确保总合成时长足够长。
1.10.4. 背景音乐没声音
可能原因有两个:
- 文件找不到(404):检查
src路径是否正确,music.mp3是否存在于index.html同级目录。 - 浏览器自动播放限制:需要用户手势触发,参考第 7 节的 click-to-play 方案。
1.10.5. 渲染报 “Video has sparse keyframes” 警告
原因:视频的关键帧间距过大(如截图中显示 max interval: 20.13s),导致 seek 失败、画面冻结。
修复:使用 FFmpeg 重编码(参考第 8 节预处理命令)。
1.10.6. render 命令报 “Not a directory”
原因:传入了 index.html 文件路径而不是目录路径。
# ❌ 错误
npx hyperframes render video/product-intro/index.html
# ✅ 正确
npx hyperframes render video/product-intro
1.11. 实战案例:DueSight App 产品介绍视频
1.11.1. 目标
- 时长 35 秒
- 3 个场景:氛围开场 → App 展示(手机 mockup + 25s 应用录屏)→ CTA 结尾
- 浅色背景(
#EEEDF8) - 背景音乐
1.11.2. 手机 Mockup 实现
手机外壳完全用 CSS 实现,不依赖任何图片资源:
<div id="phone-wrap">
<div id="phone-frame">
<div class="phone-btn" id="btn-vol-up"></div>
<div class="phone-btn" id="btn-vol-dn"></div>
<div class="phone-btn" id="btn-power"></div>
<div id="phone-screen">
<video id="app-video" data-start="6" data-duration="25"
data-track-index="1" src="video-fixed.mp4" muted playsinline></video>
</div>
<div id="phone-island"></div>
</div>
</div>
#phone-frame {
width: 390px; height: 840px;
background: linear-gradient(160deg, #2C2C42 0%, #1A1A2C 100%);
border-radius: 56px;
box-shadow:
0 70px 180px rgba(26,26,46,0.28),
0 24px 64px rgba(26,26,46,0.16),
inset 0 1px 0 rgba(255,255,255,0.13);
}
1.11.3. 执行步骤
① 安装 HyperFrames skill:
npx skills add heygen-com/hyperframes
② 在 Claude Code 中生成 HTML:
Using /hyperframes, create a 25-second product intro with a fade-in title,
a background video, and background music.
③ 准备素材(可选):
在 HTML 所在目录放入 video.mp4、music.mp3、images/。
- 没有
video.mp4/music.mp3→ 背景只显示#EEEDF8浅色,文字动画正常运行,完全可以用 - 有的话放进同一目录
video/product-intro/就会自动加载
④ 安装 HyperFrames CLI 并导出 MP4:
npm install -g @hyperframes/cli
cd /Users/Work/iOS_Projects/DueSight
npx hyperframes render video/product-intro --output video/product-intro/product-intro.mp4
推荐做法:先在浏览器里预览满意,再安装 HyperFrames 导出 MP4。
如果你只是想做 App Store 预览视频,视频背景不是必须的,浅色纯底效果本身就很干净。
1.11.4. 渲染结果
渲染后的视频包含完整的手机动效、App 录屏同步播放,以及字体渲染——全部通过 HTML/CSS 实现,无需设计工具。
1.12. 实战案例:徐霞客游庐山日记水墨学习视频
1.12.1. 目标
- 时长 72 秒
- 6 个场景:题目 + 5 段原文(配现代汉语注释)
- 水墨风格(
#0C100B深色背景、金色高亮、青绿注释) - CSS 多层山体剪影(
clip-path: polygon)视差背景 - 古风字体(ZCOOL 小薇 + Noto Serif SC)
1.12.2. 山体背景实现
三层山体叠加,产生远近纵深感:
#mtn-far {
position: absolute;
bottom: 0; left: 0;
width: 1920px; height: 520px;
background: #131A11;
clip-path: polygon(
0 100%, 0 55%,
180px 42%, 360px 58%, 540px 28%, 680px 48%,
820px 18%, 960px 38%, 1100px 22%, 1240px 44%,
1380px 30%, 1520px 50%, 1680px 35%, 1800px 52%,
1920px 42%, 1920px 100%
);
}
// 视差:三层山体以不同速度漂移
tl.to("#mtn-far", { x: -40, duration: 72, ease: "none" }, 0);
tl.to("#mtn-mid", { x: -70, duration: 72, ease: "none" }, 0);
tl.to("#mtn-near", { x: -100, duration: 72, ease: "none" }, 0);
1.12.3. 古风人物插图版(v2)
在文字版基础上,v2 引入了从 Pollinations.ai 生成的古风水墨人物插图:
- 6 个场景各一幅:站立、穿越石缝、远眺、峰顶、探崖、执笔
- 人物图片使用
mix-blend-mode: screen与深色背景融合,黑色部分透明消融 - Pollinations.ai 请求参数中指定
pure black background white ink brush strokes,配合 screen 模式产生水墨融入效果
.char-figure {
position: absolute;
right: 70px; bottom: 0;
width: 520px;
mix-blend-mode: screen; /* 黑色背景透明化 */
filter: contrast(1.12) brightness(0.92) sepia(0.08);
}
<img id="s2-char" class="char-figure"
src="https://image.pollinations.ai/prompt/ancient+Chinese+explorer+squeezing+through+narrow+rocky+gorge+...+white+ink+brush+painting+pure+black+background+guofeng?width=520&height=1040&seed=3002&nologo=true&model=flux"
alt="" crossorigin="anonymous" />
1.13. 总结
HyperFrames 代表了一种新的视频制作思路:把视频当代码来写。
它特别适合技术背景的创作者,以及需要将设计系统(Design Token、品牌色、字体)精确落地到视频内容的场景。
选择 HyperFrames 的最佳时机:
- 你已经有前端开发经验,不想学 After Effects
- 视频内容以文字、数据、UI 界面展示为主
- 需要批量生成风格一致的视频
- 视频内容需要版本管理和团队协作
不适合的场景:
- 需要大量真人拍摄素材剪辑
- 需要 3D 建模渲染
- 需要实时合成(HyperFrames 是离线渲染)
总之,HyperFrames 结合 Claude Code 这种方式制作 AppStore 宣传视频,效果还是不错的,但不适合制作长视频——比如《徐霞客游记》这样的内容,人物、动画效果还是不够好。
不过,相信未来随着 AI 的发展,创作视频的门槛会越来越低。
在这样一个 AI 日新月异的时代里,真正被重新定义的不仅是我们的工作方式,还有我们对生产力和创造力的理解。
AI 不会取代人类对美的判断、对品牌的洞察、对战略的规划,但它的到来却让每个人都有机会更加专注于这些最具价值的能力。
我们需要好好经营的,是自己的品味以及决策力。有了这个,在 AI 时代,我们方能有所作为。
文章基于 HyperFrames 实战经验整理,案例代码均经过真实渲染验证。
工具链:HyperFrames + GSAP 3.14 + FFmpeg 7.1
参考链接: