乐于分享
好东西不私藏

我深度读了 HyperFrames 源码:为什么它能把 HTML 直接渲染成视频?

我深度读了 HyperFrames 源码:为什么它能把 HTML 直接渲染成视频?

我深度读了 HyperFrames 源码:为什么它能把 HTML 直接渲染成视频?

如果你做过“把网页录成视频”,大概率都踩过同一类坑:

网页动画在浏览器里看着很顺,真到导出视频时,却开始出问题——某一帧丢了、某个动画没对齐、某段音频晚了半拍,最后得到一个“看起来差不多,但就是不够稳”的结果。

问题的根子其实不复杂:

网页动画是活的,视频帧是死的。

浏览器里的 CSS Animation、GSAP、Lottie、Three.js、音频、视频、异步资源加载,都有自己的时间系统。你按下录制按钮,看起来是在抓画面,但本质上是在追一个持续往前跑的 runtime。只要机器慢一点、资源晚一点、某一帧掉了,最终视频就可能出现抖动、错位、音画不同步。

而 HyperFrames 真正解决的,不是“怎么录屏”,而是一个更底层的问题:

如何把 HTML / CSS / JS 动画,改造成一个可以按帧 seek、可重复、可编码的视频渲染系统。

这也是它值得读源码的地方。它不是一个简单的 Puppeteer 截图脚本,而是一套由 runtime、timeline、seek protocol、frame capture、audio mixer 和 ffmpeg pipeline 组合起来的渲染架构。

图注:HyperFrames 的核心思想,是把浏览器从“实时播放环境”改造成“确定性帧渲染环境”。


先给结论:HyperFrames 到底是什么?

如果只用一句话概括,我会这么说:

HyperFrames 让网页继续在浏览器里运行,但把“时间控制权”从浏览器手里夺了回来。

普通网页动画的模型是这样的:

真实时间前进 -> 动画系统自动更新 -> 浏览器绘制画面

HyperFrames 要的不是这个。

它要的是:

渲染器指定 time -> runtime seek 所有动画 -> 浏览器绘制确定画面 -> capture 当前帧

这两个模型的区别,几乎决定了最终视频能不能稳定导出。

所以它的关键不是“播放页面再截图”,而是:

每到一个目标时间点,就把页面里所有动画系统都拨到那个时间点,然后只截这一帧。

这就是它整个架构的出发点。


导读:这篇文章会讲什么

如果你只是想快速抓重点,先看这 4 句:

  • HyperFrames 是一个 HTML-first 的视频渲染框架
  • 它最核心的技术,不是录屏,而是 runtime + adapters + seek
  • 它把渲染流程拆成了完整生产线:compile → probe → extract → audio → capture → encode → assemble
  • 它最有意思的部分,是如何把 GSAP、CSS、WAAPI、Anime.js、Lottie、Three.js 接到同一条时间线上。

下面我们就按这个顺序拆开看。


一、从仓库结构看设计分层

HyperFrames 是一个 monorepo,核心模块大致可以分成几层:

  • core:负责 runtime、compiler、linter、timeline、adapters。
  • engine:负责 Puppeteer/Chrome capture、browser manager、ffmpeg、audio mixer、parallel capture。
  • producer:负责多阶段 render pipeline,包括 compile、probe、extract、audio、capture、encode、assemble。
  • cli:面向命令行使用者,封装项目入口和渲染命令。
  • player:用于预览和播放渲染结果。
  • studio:偏向可视化创作或调试体验。
  • shader-transitions:提供更复杂的视觉转场能力。

你会发现,它不是那种“一个库包打天下”的设计。

它明显在做分层:

  • core 定义“网页怎么变成可控时间轴”
  • engine 解决“怎么把帧和音频真正产出来”
  • producer 负责“把整个渲染过程编排成流水线”

图注:HyperFrames 的重点不在某一个明星模块,而在 core、engine、producer 三层如何配合。

一个更直观的理解

你可以把这三层想成:

  • core:理解页面
  • engine:生成结果
  • producer:组织生产

这个分工很朴素,但很重要。因为“网页转视频”这件事,如果不拆层,后面几乎一定会混成一锅粥。


二、Core:它真正做的是“把页面编译成时间线”

core 是 HyperFrames 最关键的一层,因为它处理的是一句话:

浏览器里的内容,如何被机器理解、控制,并且稳定复现。

它大致承担四类工作。

1)编译 timing 信息

页面里可能有很多片段、元素、媒体资源。HyperFrames 的 timing compiler 会补齐关键元数据,例如:

  • data-end
  • data-duration
  • data-has-audio

如果遇到 unresolved media duration,也就是静态信息里没法直接知道长度的媒体资源,它会继续去 probe 实际时长。

这一步很关键。

因为视频渲染最怕的,不是“看起来差一点”,而是时间基准错了。一个片段到底持续 3 秒还是 3.4 秒,影响的不是这一段,而是后面整条时间线。

2)建立 timeline

Timeline 是全局调度的基础。

它不是普通意义上的“时间轴 UI”,而是运行时真正依赖的时间语义层:

  • 哪个元素什么时候出现
  • 哪个媒体什么时候开始播放
  • 哪段动画在第几秒应该处于什么状态

后续无论是预览、截图还是编码,实际上都是围绕这条 timeline 在做事情。

3)注入 runtime

Runtime 会在浏览器环境中暴露两个关键对象:

  • window.__timelines
  • window.__hyperframes

并且会在 DOMContentLoaded 之后初始化。

这个时机选得很合理:

  • DOM 已经可用
  • 页面结构可以扫描
  • 动画对象也有机会被挂载或收集

换句话说,HyperFrames 不是在“浏览器之外”控制页面,它是在浏览器内部建立一个新的时间控制层

4)提供 adapters

这是 core 最有技术含量的一块。

因为浏览器里并不存在一个统一 API,可以同时控制:

  • GSAP
  • CSS Animation
  • WAAPI
  • Anime.js
  • Lottie
  • Three.js

HyperFrames 做的事,不是强行把这些库改成一个东西,而是分别理解它们的时间模型,再把它们接到同一套 seek protocol 上。

这就是 adapter 层存在的意义。


三、Runtime:真正的“时间接管者”

如果只看表面,HyperFrames 像是在用 Puppeteer 打开网页,然后截图。

但真正让它成立的,不是截图,而是 runtime。

Runtime 的任务不是“让动画播放”,而是:

阻止动画自由播放,并让它们服从外部时间。

这两者差别非常大。

普通网页动画的问题在于,它们默认都在追真实时间:

  • CSS 自己播
  • GSAP timeline 自己推
  • Lottie 自己更新帧
  • Three.js 可能挂在 requestAnimationFrame 上不停渲染

而视频渲染需要的是:

  • 给我第 0 帧
  • 给我第 24 帧
  • 给我第 137 帧
  • 每次都必须一样

所以 runtime 的核心能力可以概括成 4 件事:

  • 暂停或接管动画
  • 接收外部传入的目标时间
  • 把目标时间分发给不同 adapter
  • 等页面状态稳定后,再让 capture 层截图

为什么这一步这么值钱?

因为这一步一旦做成,HyperFrames 就不再是“录屏工具”,而更像一个:

浏览器里的确定性渲染器。

录屏是在追时间。
HyperFrames 是在调度时间。

这两种思路,看起来只差几个词,实际差的是一整个系统级别的稳定性。


四、适配器技术:这是整个项目最值得看的部分

如果要我选一个 HyperFrames 最值得细读的地方,我会选 adapter。

原因很简单:

前端动画生态太碎了。

CSS Animation 和 GSAP 的时间控制方式不一样,Lottie 和 Three.js 又是完全不同的渲染模型。HyperFrames 没有试图把它们“统一改写”,而是做了一件很工程、也很聪明的事:

保留各自原生能力,只统一“seek 到某个时间点”这件事。

图注:Adapter 层把不同动画系统的时间控制 API,翻译成 HyperFrames runtime 可以统一调用的 seek 行为。

GSAP adapter:暂停 timeline,再精确 seek

GSAP 本身就有成熟的 timeline 模型,所以它是最适合被 HyperFrames 接管的一类动画系统。

核心思路很直接:

  • pause
  • 再通过 totalTimeseek 定位到目标时间

这样做的妙处在于:

GSAP 复杂的 easing、stagger、nested timeline,仍然交给 GSAP 自己处理;HyperFrames 只负责告诉它:

“你现在应该在第几秒。”

这是非常典型的好设计:

不重写动画引擎,只接管时钟。

CSS adapter:优先用 Web Animations,必要时回退到负 animation-delay

CSS Animation 看起来最“原生”,但其实并不好管。

因为它默认由浏览器自己根据真实时间推进,不像 GSAP 那样天然有清晰的 timeline 对象。

HyperFrames 的 CSS adapter 优先依赖:

  • getAnimations()
  • currentTime
  • pause()

也就是说,它会尽量通过浏览器提供的动画对象去暂停 CSS 动画,再直接设置 currentTime

但工程上真正好的地方,在于它没有赌这一条路一定成立。

当动画对象不好拿、或者环境不稳定时,它还准备了 fallback:

  • 使用负的 animation-delay 来模拟时间偏移

这个 fallback 很“工程师”。

它不优雅,但它靠谱。

对于视频渲染系统来说,这一点比抽象是否漂亮更重要。

WAAPI adapter:用 document.getAnimations() 统一收集动画

WAAPI,也就是 Web Animations API,本身就提供了更明确的动画控制接口。

HyperFrames 的 WAAPI adapter 核心非常清楚:

  • 通过 document.getAnimations() 收集页面动画
  • 暂停动画
  • 设置每个 animation 的 currentTime

它和 CSS adapter 有交集,但 WAAPI 的价值在于:

它让“动画时间”这件事,真正变成了浏览器原生可写状态

对于确定性渲染来说,这几乎就是黄金接口。

Anime.js adapter:依赖 window.__hfAnime 管理实例

Anime.js 的接入方式更偏约定式。

HyperFrames 依赖 window.__hfAnime 来收集或暴露 Anime.js 实例,然后对实例调用:

instance.seek(timeMs)

这里有个细节非常重要:单位是毫秒。

在多 adapter 系统里,单位转换其实是脏活累活:

  • 有的库用秒
  • 有的库用毫秒
  • 有的库用帧号
  • 有的库用百分比进度

这些复杂度如果泄漏给 pipeline,整个系统很快会变得难以维护。

所以 adapter 的职责之一,就是把这些差异吃掉。

Lottie adapter:同时兼容 lottie-web 和 dotlottie-web

Lottie 的特点在于,它不是普通 DOM 动画,而是一个动画播放器

HyperFrames 的 Lottie adapter 同时兼容:

  • lottie-web
  • dotlottie-web

并通过 window.__hfLottie 进行接入。

Lottie 真正的难点,不是“能不能播放”,而是:

如何把全局时间转换成 Lottie 能理解的播放位置。

因为播放器内部通常有自己的:

  • 帧率
  • 进度
  • 渲染状态

所以在每次 capture 前,HyperFrames 需要先把 Lottie 跳到正确画面,再截图。

对于产品演示、动效 Logo、数据可视化动效,这个 adapter 非常实用。很多视频生成场景并不只靠 CSS 或 canvas,Lottie 恰恰是设计资产进入工程系统的重要桥梁。

Three.js adapter:不是控制动画,而是把时间推给渲染逻辑

Three.js 和前面几类都不一样。

它不是“动画库”,而是一个 3D 渲染框架。你的场景可能在 requestAnimationFrame 里更新:

  • 相机
  • 材质
  • 粒子
  • shader uniform
  • 自定义物理逻辑

HyperFrames 的 Three.js adapter 用了两个关键机制:

  • window.__hfThreeTime
  • hf-seek 事件

也就是说,runtime 会把当前时间写进全局变量,并派发 seek 事件,让 Three.js 侧逻辑知道:

“现在该渲染哪个时间点。”

这套设计最聪明的地方,在于它没有假装“我可以自动理解所有 3D 场景”。

它没有过度承诺,而是给开发者一个很清楚的协议:

  • 你负责让 Three.js 场景响应时间
  • HyperFrames 负责在每一帧把时间准确送到

对复杂图形系统来说,这比强行封装靠谱得多。


五、Linter:在渲染前把坑提前挖出来

视频渲染最怕的,不是慢,而是跑到一半才发现错了。

HyperFrames 的 linter 会做两类检查:

  • 静态规则检查
  • URL 可访问性检查

静态规则检查可以提前发现:

  • 结构问题
  • 属性问题
  • 时间配置问题

URL 可访问性检查,则是在渲染前尽量确认资源真的能被拿到。

这一步看上去不如 runtime 酷,但对自动化链路非常重要。因为在批量生成视频、Agent 调用渲染器、定时跑内容生产任务时,失败成本远高于“我再点一次按钮”。

很多时候,真正让系统可用的,不是最亮眼的模块,而是这些提前挡坑的基础设施。


六、Engine:Puppeteer、Chrome、截图与音频混合

engine 负责把已经可 seek 的页面,真正渲染成可交付的视频资产。

这里至少有 3 个关键点。

1)Capture 基于 Puppeteer 和 Chrome

HyperFrames 会让浏览器进入受控渲染环境,然后按时间点驱动页面状态,再抓取画面。

也就是说,它不是“把一个正在播放的页面录下来”,而是:

  • 指定时间
  • 让页面到达该状态
  • 抓这一帧

2)Capture 支持两种模式

  • beginframe
  • screenshot

screenshot 很容易理解,就是让浏览器截图。
beginframe 更接近底层帧推进方式,适合追求更稳定或更精细的帧控制。

这说明 HyperFrames 在实现层并不是单一路线,而是给不同场景留了更细的控制面。

3)音频不是录屏顺带出来的

这一点很关键。

HyperFrames 会独立处理音频并混音,而不是指望屏幕录制把声音一起抓下来。

为什么要这么做?

因为视频画面可以逐帧 seek,但音频是连续信号。

如果你把音频也当成“录屏副产品”,你会很难保证:

  • 时间精度
  • 混音控制
  • 输出稳定性

而独立做 audio extract / mix,再在最终编码阶段与画面合成,才更接近专业视频管线的做法。


七、Producer:把渲染拆成一条生产流水线

producer 是连接 core 和 engine 的编排层。

它不是单纯“调几个函数”,而是在组织一条完整的渲染生产线:

compile -> probe -> extract -> audio -> capture -> encode -> assemble

图注:HyperFrames 把视频生成拆成多个阶段,分别处理结构编译、资源探测、音频、帧捕获、编码与封装。

每一段在做什么?

  • compile:把页面和配置变成可渲染结构
  • probe:探测媒体资源和时长
  • extract:提取渲染需要的中间资产
  • audio:单独处理音频与混音
  • capture:按帧产出画面
  • encode:调用 ffmpeg 编码
  • assemble:组装最终产物

最终输出可以是:

  • mp4
  • webm
  • mov
  • png-sequence

这条流水线为什么设计得好?

因为它天然具备 3 个工程优势:

  • 可维护:每阶段职责明确
  • 可重试:某一步失败,不一定推倒重来
  • 可扩展:未来支持新输出格式,主要改 encode / assemble 即可

比如 capture 出了问题,不一定需要重新做所有前置步骤;音频处理升级,也不必改 runtime。

这就是分阶段 pipeline 的好处:复杂,但不乱。


八、为什么这套设计特别适合 AI 视频生成?

我觉得 HyperFrames 很适合放进 Agent 或自动化内容生产链路里,原因主要有 3 个。

1)HTML 是一个非常强的中间表示

LLM 很擅长生成结构化文本,而 HTML / CSS / JS 又是成熟的视觉表达系统。

用 HTML 描述视频画面,相比直接生成底层图形指令,有两个天然优势:

  • 模型更容易写
  • 人类更容易改

这点在 AI 工作流里非常重要。

2)浏览器本身就是成熟的渲染引擎

字体、布局、图片、SVG、canvas、WebGL、视频、动效,浏览器已经把这些能力都准备好了。

HyperFrames 没有重新发明这些能力,而是做了更聪明的事:

把浏览器变成一个可控的视频帧生成器。

3)确定性 seek 是自动化的前提

如果一个视频生成系统每次跑出来都不一样,Agent 就很难稳定调试、回放和修复。

HyperFrames 通过 runtime 和 adapters 把动画时间统一起来,本质上是在给自动化生成建立一个可验证、可重复的执行环境。

这件事,远比“能生成一条视频”更重要。


九、我的评价:它更像“浏览器视频编译器”

我会把 HyperFrames 理解成一个:

浏览器视频编译器

而不是录屏工具。

它真正的价值,不在于“能不能把网页变成视频”,而在于它把这件事拆成了清晰的工程问题:

  • 页面结构如何编译
  • 时间线如何生成
  • 动画系统如何 seek
  • 媒体时长如何探测
  • 帧如何捕获
  • 音频如何混合
  • 产物如何编码与封装

这套思路尤其适合:

  • AI 生成短视频
  • 产品 Demo 自动化生成
  • 数据可视化视频导出
  • 课程、报告、营销物料批量生产
  • 用前端技术栈搭建可编程视频系统

当然,它也有边界。

如果页面里有大量无法控制的随机逻辑、依赖真实网络状态的动态内容、没有按 seek protocol 改造的 Three.js 场景,最终结果仍然可能不稳定。HyperFrames 提供的是一套确定性渲染框架,但页面本身也必须配合这套框架来写。

这恰恰也是我喜欢它的一点:

它没有把“网页转视频”包装成一个魔法按钮,而是非常诚实地把复杂度摆出来,然后一层一层解决。


最后

读完 HyperFrames 的架构,我最大的感受是:

它真正有意思的地方,不是“又一个视频工具”,而是它让前端世界里那些本来只属于浏览器运行时的东西——DOM、CSS、动画库、WebGL、媒体元素——第一次以一种相对完整、相对工程化的方式,进入了视频生产线。

core 接管时间,engine 生成帧和音频,producer 组织流水线,ffmpeg 完成最终封装。

这条链一旦打通,HTML 就不再只是网页。

它也可以是:

可编程的视频场景描述语言。

而 HyperFrames 做的,正是在前端工程和视频生成之间,搭了一座很像样的桥。