我深度读了 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-enddata-durationdata-has-audio
如果遇到 unresolved media duration,也就是静态信息里没法直接知道长度的媒体资源,它会继续去 probe 实际时长。
这一步很关键。
因为视频渲染最怕的,不是“看起来差一点”,而是时间基准错了。一个片段到底持续 3 秒还是 3.4 秒,影响的不是这一段,而是后面整条时间线。
2)建立 timeline
Timeline 是全局调度的基础。
它不是普通意义上的“时间轴 UI”,而是运行时真正依赖的时间语义层:
哪个元素什么时候出现 哪个媒体什么时候开始播放 哪段动画在第几秒应该处于什么状态
后续无论是预览、截图还是编码,实际上都是围绕这条 timeline 在做事情。
3)注入 runtime
Runtime 会在浏览器环境中暴露两个关键对象:
window.__timelineswindow.__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再通过 totalTime或seek定位到目标时间
这样做的妙处在于:
GSAP 复杂的 easing、stagger、nested timeline,仍然交给 GSAP 自己处理;HyperFrames 只负责告诉它:
“你现在应该在第几秒。”
这是非常典型的好设计:
不重写动画引擎,只接管时钟。
CSS adapter:优先用 Web Animations,必要时回退到负 animation-delay
CSS Animation 看起来最“原生”,但其实并不好管。
因为它默认由浏览器自己根据真实时间推进,不像 GSAP 那样天然有清晰的 timeline 对象。
HyperFrames 的 CSS adapter 优先依赖:
getAnimations()currentTimepause()
也就是说,它会尽量通过浏览器提供的动画对象去暂停 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-webdotlottie-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.__hfThreeTimehf-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 支持两种模式
beginframescreenshot
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:组装最终产物
最终输出可以是:
mp4webmmovpng-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 做的,正是在前端工程和视频生成之间,搭了一座很像样的桥。
夜雨聆风