跳到主要内容
BLKBLKTECH

指南 / ai

HyperFrames Skills 实战:从 Brief 到 MP4 生成第一条视频

跟随完整案例,使用 HyperFrames Skills 完成需求确认、分镜、HTML Composition、动画检查、关键帧预览和 MP4 渲染。

作者 BLKTECH 编辑部更新 2026年7月25日22 分钟难度 实战低成本HyperFramesHTMLCSSGSAPFFmpegAI Agent

使用 HyperFrames Skills 生成视频的可靠流程是:先确认 Brief 和 Storyboard,再编写带时间属性的 HTML Composition 与可 Seek 动画,依次执行 lint、check、snapshot 和 preview,人工确认后才进行高质量 render。

这篇教程要完成什么

上一篇《HyperFrames 原理详解》解释了浏览器、时间轴和 FFmpeg 如何协作。这一篇不再停留在概念层,而是使用 HyperFrames Skills 完成一条可以渲染的视频。

我们要制作一条 15 秒、1920×1080 的横屏解释视频,主题是:

网页如何变成视频?

最终内容分成三幕:

时间 场景 任务
0–4 秒 提出问题 “网页,也可以成为视频?”
4–11 秒 展示流程 HTML → Browser → MP4
11–15 秒 总结收尾 “Write the web. Render the video.”

这不是为了展示最复杂的动效,而是建立一条完整、可重复的工作流:

需求 → Brief → Storyboard → Composition
→ lint → check → snapshot → preview → render

开始前的环境准备

HyperFrames CLI 需要现代 Node.js 环境和 FFmpeg。以当前 Skills 的技术合约为准,建议准备:

  • Node.js 22 或更高版本
  • npm / pnpm / bun 中任意一个包管理器
  • FFmpeg 与 ffprobe
  • 已安装的 HyperFrames Skills
  • 一个可以让 Agent 读写的独立项目目录

先检查本地环境:

node --version
npx hyperframes --version
ffmpeg -version
ffprobe -version

如果已经有 HyperFrames 项目,还应该先确认项目固定的 CLI 版本是否需要升级:

npx hyperframes@latest upgrade --project . --check

这条检查不会直接替你升级项目。视频工程最好固定 CLI 和依赖版本,避免同一个项目在不同时间使用不同运行时渲染。

第一步:创建项目,而不是直接写动画

在一个干净目录中初始化项目:

npx hyperframes init hf-first-video
cd hf-first-video

不同 CLI 版本提供的示例和初始化选项可能不同。如果 Agent 在非交互环境执行,可以先查看:

npx hyperframes init --help

不要一上来就要求 Agent “直接写一个炫酷 HTML”。先让它读取当前项目状态和已经安装的 Skills:

这是一个新的 HyperFrames 项目。
请先检查项目文件、CLI 版本和可用 Skills,暂时不要开始渲染。
确认当前项目应该采用的工作流,并列出你准备生成的项目文件。

这样做可以避免 Agent 忽略现有模板、设计规范或项目里的 hyperframes.json

第二步:给 Agent 一份可执行的需求

第一次实战可以直接使用下面这份 Prompt:

使用 HyperFrames 制作一条 15 秒、1920×1080、30 FPS 的横屏解释视频。

主题:网页如何变成视频。
目标观众:了解 HTML/CSS,但没有使用过代码视频框架的开发者。
核心信息:HyperFrames 用浏览器渲染 HTML 画面,再输出为视频。

场景结构:
1. 0–4 秒:标题“网页,也可以成为视频?”
2. 4–11 秒:依次展示 HTML、Browser、MP4 三个阶段和连接关系
3. 11–15 秒:总结“Write the web. Render the video.”

视觉要求:
- 深色背景
- 蓝绿色高亮
- 大字号、少文字
- 每一幕只保留一个视觉中心
- 不使用普通 SaaS 卡片墙布局
- 动画克制、清晰、有停顿

工程要求:
- 遵守 hyperframes-core Composition 合约
- 使用一个同步创建的 paused GSAP timeline
- 不使用 Math.random、Date.now、无限循环或渲染时网络请求
- 关键素材使用本地文件
- 先生成 BRIEF.md 和 STORYBOARD.md
- Storyboard 确认后再完成 Composition
- 完成后运行 lint、check 和关键时间点 snapshot
- 在最终 preview 确认前不要渲染高质量 MP4

这份 Prompt 同时定义了内容、视觉和工程验收标准。它比“做一个科技感视频”更容易得到稳定结果。

第三步:理解 BRIEF、STORYBOARD 和 frame.md

一个成熟的 HyperFrames 工作流,通常不是从 index.html 开始。

项目文件可以分成四层:

BRIEF.md        为什么做、给谁看、必须表达什么
STORYBOARD.md   每一幕展示什么、持续多久、如何衔接
frame.md        颜色、字体、间距、构图和视觉约束
compositions/   最终可渲染的视频工程

BRIEF.md:锁定目标

我们的 Brief 可以简化成:

---
workflow: general-video
flow: companion
storyboard: yes
message: "HyperFrames 用浏览器把 HTML 渲染成视频"
destination: "技术内容站与社交媒体"
aspect: "16:9"
language: "zh-CN"
audience: "了解前端开发的 AI 工具用户"
length: "15s"
---

## Goal

让观众在 15 秒内理解 HTML → Browser → MP4 的核心路径。

## Must include

- HTML、Browser、MP4 三个阶段
- “网页即视频”的结论

## Avoid

- 小字号说明文字
- 大量卡片和装饰
- 与核心流程无关的 3D 效果

具体字段应该以当前安装的 Skill 生成结果为准。重点不是手写出完全固定的 YAML,而是把需求确认结果保存成项目文件,避免 Agent 在后续阶段重新猜测。

STORYBOARD.md:先确定节奏

# Storyboard

## Scene 01 — Question

- Time: 0–4s
- Visual: 黑色画布中央出现大标题
- Text: 网页,也可以成为视频?
- Motion: 标题上移淡入,问号最后出现
- Exit: 标题整体缩小,为下一幕让出空间

## Scene 02 — Pipeline

- Time: 4–11s
- Visual: HTML、Browser、MP4 从左到右依次建立
- Motion: 节点进入,连接线绘制,MP4 节点高亮
- Dwell: 完整流程至少停留 1.2 秒

## Scene 03 — Summary

- Time: 11–15s
- Visual: 流程收束成一句英文结论
- Text: Write the web. Render the video.
- Motion: 两段文字分两拍进入

Storyboard 最重要的价值不是写得漂亮,而是提前发现:

  • 15 秒是否放了太多内容
  • 每一幕是否有明确视觉中心
  • 动画结束后是否有时间让观众看清
  • 场景之间是否存在节奏断裂

frame.md:固定视觉语言

一个最小设计规范可以写成:

---
background: "#07110F"
foreground: "#F4FFF9"
accent: "#48F7B2"
muted: "#799388"
fontHeading: "Inter"
fontBody: "Inter"
cornerRadius: "24px"
---

- 主标题 88–124px
- 画面安全边距不小于 96px
- 每幕最多一个高亮色焦点
- 不使用半透明卡片墙
- 阴影只用于分离前后层,不做装饰

有了 frame.md,Agent 在生成第二幕时就不需要重新发明一套颜色和字号。

第四步:构建最小可渲染 Composition

下面是一份适合解释原理的精简结构。真实项目可以拆成多个文件,但第一次练习先看清完整 Composition。示例假设 GSAP 已按项目依赖或现有脚手架的方式保存为 ./vendor/gsap.min.js;实际项目应优先沿用脚手架已经配置好的加载方式,不要在渲染阶段临时依赖远程 CDN。

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=1920, height=1080" />
    <title>Web to Video</title>
    <script src="./vendor/gsap.min.js"></script>
    <style>
      * { box-sizing: border-box; }

      html, body {
        margin: 0;
        width: 1920px;
        height: 1080px;
        overflow: hidden;
        font-family: Inter, system-ui, sans-serif;
        color: #f4fff9;
      }

      #root {
        position: relative;
        width: 1920px;
        height: 1080px;
        overflow: hidden;
      }

      .clip {
        position: absolute;
        inset: 0;
      }

      .frame-bg {
        position: absolute;
        inset: 0;
        background:
          radial-gradient(circle at 50% 45%, #13352c 0, #07110f 48%, #030706 100%);
      }

      .scene-content {
        position: absolute;
        inset: 0;
        display: grid;
        place-items: center;
        padding: 96px;
      }

      .hero-title {
        max-width: 1400px;
        margin: 0;
        font-size: 118px;
        line-height: 0.98;
        letter-spacing: -0.055em;
        text-align: center;
      }

      .pipeline {
        display: flex;
        align-items: center;
        gap: 52px;
      }

      .node {
        display: grid;
        place-items: center;
        width: 340px;
        height: 220px;
        border: 2px solid #48f7b2;
        border-radius: 32px;
        font-size: 58px;
        font-weight: 700;
      }

      .arrow {
        color: #48f7b2;
        font-size: 72px;
      }

      .summary {
        font-size: 92px;
        line-height: 1.05;
        letter-spacing: -0.045em;
        text-align: center;
      }

      .accent { color: #48f7b2; }
    </style>
  </head>
  <body>
    <div
      id="root"
      data-composition-id="main"
      data-start="0"
      data-width="1920"
      data-height="1080"
      data-duration="15"
    >
      <div
        id="main-background"
        class="clip"
        data-start="0"
        data-duration="15"
        data-track-index="0"
      >
        <div class="frame-bg"></div>
      </div>

      <section
        id="main-question"
        class="clip"
        data-start="0"
        data-duration="4"
        data-track-index="1"
      >
        <div id="question-content" class="scene-content">
          <h1 class="hero-title">网页,也可以成为<span class="accent">视频</span>?</h1>
        </div>
      </section>

      <section
        id="main-pipeline"
        class="clip"
        data-start="4"
        data-duration="7"
        data-track-index="1"
      >
        <div id="pipeline-content" class="scene-content">
          <div class="pipeline">
            <div id="node-html" class="node">HTML</div>
            <div id="arrow-one" class="arrow">→</div>
            <div id="node-browser" class="node">Browser</div>
            <div id="arrow-two" class="arrow">→</div>
            <div id="node-mp4" class="node">MP4</div>
          </div>
        </div>
      </section>

      <section
        id="main-summary"
        class="clip"
        data-start="11"
        data-duration="4"
        data-track-index="1"
      >
        <div id="summary-content" class="scene-content">
          <div class="summary">
            <div id="summary-line-one">Write the web.</div>
            <div id="summary-line-two" class="accent">Render the video.</div>
          </div>
        </div>
      </section>
    </div>

    <script>
      window.__timelines = window.__timelines || {};

      const tl = gsap.timeline({ paused: true });

      tl.from("#question-content", {
        y: 70,
        opacity: 0,
        duration: 0.8,
        ease: "power3.out"
      }, 0.3);

      tl.to("#question-content", {
        scale: 0.92,
        opacity: 0,
        duration: 0.55,
        ease: "power2.in"
      }, 3.25);

      tl.from(["#node-html", "#node-browser", "#node-mp4"], {
        y: 64,
        opacity: 0,
        duration: 0.65,
        stagger: 0.42,
        ease: "power3.out"
      }, 4.35);

      tl.from(["#arrow-one", "#arrow-two"], {
        scaleX: 0,
        opacity: 0,
        duration: 0.45,
        stagger: 0.42,
        ease: "power2.out"
      }, 4.9);

      tl.to("#node-mp4", {
        scale: 1.08,
        backgroundColor: "#48f7b2",
        color: "#07110f",
        duration: 0.45,
        ease: "power2.inOut"
      }, 7.2);

      tl.to("#pipeline-content", {
        y: -36,
        opacity: 0,
        duration: 0.55,
        ease: "power2.in"
      }, 10.2);

      tl.from("#summary-line-one", {
        y: 52,
        opacity: 0,
        duration: 0.65,
        ease: "power3.out"
      }, 11.35);

      tl.from("#summary-line-two", {
        y: 52,
        opacity: 0,
        duration: 0.65,
        ease: "power3.out"
      }, 11.75);

      window.__timelines["main"] = tl;
    </script>
  </body>
</html>

这份代码遵守了哪些规则

  • 根 Composition 有明确宽高、起点和总时长
  • 可见时间片段都带有 class="clip"
  • 背景放在全画幅子元素上,而不是只依赖 Composition 根背景
  • 三个主要场景在同一轨道上不重叠
  • Timeline 使用 { paused: true }
  • Timeline 在页面加载时同步创建
  • 动画只作用于 Clip 内部包装元素,没有接管 .clip 的显示生命周期
  • 没有使用随机数、系统时间和无限循环
  • 每个 ID 在组装页面中保持唯一

这类约束看起来琐碎,却可以避免“预览正常、正式渲染黑屏”“第二幕直接显示”“子场景找错元素”等问题。

第五步:先 lint,再运行完整 check

完成第一版结构后运行:

npx hyperframes lint

lint 适合在写作过程中频繁执行,主要帮助发现结构和规范问题。

准备进入预览前,执行最终检查:

npx hyperframes check

check 会再次执行 lint,并进一步检查浏览器运行时、资源请求、布局、动效和对比度等问题。因此,没有必要在最终命令前机械地再重复一次独立 lint;写作期间 lint,最终阶段 check 即可。

如果希望把警告也作为失败条件,可以根据项目要求使用严格模式:

npx hyperframes check --strict

不要忽略警告里的时间点

假设检查结果提示:

primary text overflows canvas at t=11.4s

不要只缩小全局字号。先定位:

  • 是哪一个 Composition
  • 哪个元素
  • 哪个时间点
  • 动画是否把元素推到了画布外
  • 这是故意出画,还是实际裁切

很多布局问题只在动画峰值发生,静态页面检查不到。

第六步:用 snapshot 检查关键时间点

视频不是一张网页截图。只看第 0 秒无法判断完整结果。

为本例选择这些时间点:

npx hyperframes snapshot --at 1,3.5,5.5,8,10.5,12.5,14.5

分别检查:

时间点 应该看到什么
1s 第一幕标题已经进入
3.5s 第一幕开始退出,但文字仍可辨认
5.5s 三个流程节点正在建立
8s MP4 节点高亮
10.5s 流程准备离场
12.5s 总结两行文字完整出现
14.5s 结论保持稳定,没有提前消失

如果项目使用 Sub-composition,更应该为每个子场景选择至少一个可见中点。静态 lint 无法发现所有挂载、模板和时间轴注册问题。

第七步:打开最终预览

检查通过后运行:

npx hyperframes preview

这里要审阅的是完整 Composition 预览,而不是前期 Storyboard 看板。

重点检查:

  • 第一秒是否能抓住注意力
  • 场景切换是否过快
  • 动画结束后有没有阅读停顿
  • 标题是否像网页标题,而不是视频画面
  • 流程箭头是否真的帮助理解
  • 最后一幕是否留够收尾时间
  • 如果有旁白,文字和语音是否在同一节拍

修改意见尽量指向场景、时间和目标:

第二幕 7.2 秒的 MP4 高亮太突然。
保留布局和总时长,把高亮改成 0.7 秒的 scale + backgroundColor 过渡,
并确保完整流程在退出前停留至少 1.2 秒。

这种反馈比“再高级一点”更容易执行,也不会导致 Agent 重做无关部分。

第八步:确认后再渲染 MP4

预览确认后执行:

npx hyperframes render --quality high --output out.mp4

然后验证输出:

test -s out.mp4
ffprobe -v error -show_format -show_streams out.mp4

至少确认:

  • 文件存在且非空
  • 视频时长接近 15 秒
  • 分辨率是 1920×1080
  • 帧率符合预期
  • 如果有音频,音轨存在
  • 从头到尾可以正常播放

高质量渲染通常比 snapshot 和 preview 更耗时间,所以应该放在人工确认之后。

有配音和音乐时,流程怎样变化

如果视频包含旁白,推荐先锁定脚本,再生成语音和词级时间数据:

确认脚本

生成或导入配音

获得真实音频时长与词级时间

同步场景时长

完成画面和字幕

不要先用估算时长做完全部动画,再强行把旁白塞进去。真实语音长度应该决定旁白场景的最终时长。

音乐和画面可以并行准备,但在正式 render 前必须确认:

  • BGM 文件已经落地
  • 音量和淡入淡出已经确定
  • 旁白没有被背景音乐掩盖
  • 音效不会造成峰值过载
  • 媒体文件不依赖临时 URL

常见失败与修复方法

1. 页面全部挤在左上角

通常是根元素或祖先元素没有确定宽高。确保 Composition 根是明确的像素尺寸,不要只写 height: 100% 却不给父级高度。

2. 预览正常,渲染背景变黑

不要只把场景背景写在 Composition 根元素上。使用绝对定位、铺满画布的子元素承载背景。

3. 后面的场景一开始就闪现

不要在页面加载时用 gsap.set 修改后续 .clip。Clip 的显示时间交给 HyperFrames,动画只处理 Clip 内部内容。

4. Snapshot 中视频素材是空白

检查:

  • 媒体路径是否正确
  • 元素 ID 是否与其他文件重复
  • 视频是否完成解码
  • 是否使用框架支持的媒体声明
  • 是否错误地依赖 autoplay

5. Timeline 找不到

确认三处完全一致:

data-composition-id="main"
window.__timelines["main"]
当前 Composition 的实际 ID

6. 动画在不同渲染中不一致

搜索项目里的:

Math.random
Date.now
performance.now
setTimeout
fetch(
repeat: -1

它们不一定全部错误,但都值得检查是否参与了渲染时状态。

把这次实战保存成可复用资产

完成视频后,不要只留下一个 out.mp4

至少保留:

  • 最终 BRIEF.md
  • 最终 STORYBOARD.md
  • 设计规范 frame.md
  • Composition 源码
  • 本地媒体资产
  • 通过检查的 CLI 版本
  • 最终 Prompt 和关键修改记录
  • 关键帧截图

然后问自己:

  • 标题场景能否变成 Title Block
  • 流程场景能否通过变量替换三个节点
  • 收尾场景能否复用于下一条视频
  • 配色和字体是否应该成为品牌预设

一条视频的真正产出,不只是 MP4,还应该包括下一条视频可以直接复用的工程资产。

系列导航

  1. HyperFrames 原理详解:AI Agent 如何把 HTML 渲染成视频
  2. HyperFrames Skills 实战:从 Brief 到 MP4 生成第一条视频
  3. HyperFrames 进阶:如何提高画面质量与生产效率
  4. 玩转 HyperFrames:从 AI Coder 到 AI Layout Driver
  5. HyperFrames 官方 Registry 指南:让 Agent 自动查、拉、挂、验
  6. HyperFrames Registry 实战:Block + JSON Schema 视频流水线

配套资源:

NEXT ACTION / 下一步

继续学习 HyperFrames 进阶

把读到的方法变成一个小行动,完成后再回来迭代。

继续

RELATED / 相关推荐

接着读这些

按同一栏目、标签与技术栈为你挑选。