用 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. 规则(非常重要)

  1. 必须有转场:禁止直接跳切(jump cut)
  2. 每个场景必须有入场动画:所有元素通过 gsap.from() 动画进入,禁止直接出现
  3. 仅最后一个场景可以有退场动画:其他场景的"退出"由转场效果承担
  4. 转场后必须添加 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. 背景音乐没声音

可能原因有两个:

  1. 文件找不到(404):检查 src 路径是否正确,music.mp3 是否存在于 index.html 同级目录。
  2. 浏览器自动播放限制:需要用户手势触发,参考第 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.mp4music.mp3images/

  • 没有 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

参考链接