在浏览器中无服务端导出Canvas动画为MP4视频
本文介绍如何在纯前端环境下,将 html canvas 的实时渲染画面(如物理模拟动画)录制为视频文件,全程无需服务器,结合 mediarecorder api 与 ffmpeg.wasm 实现 webm 录制 + mp4 转码。 先说结论:在浏览器里想把Canvas动画直接导出为标准MP4,这事儿
本文介绍如何在纯前端环境下,将 html canvas 的实时渲染画面(如物理模拟动画)录制为视频文件,全程无需服务器,结合 mediarecorder api 与 ffmpeg.wasm 实现 webm 录制 + mp4 转码。
先说结论:在浏览器里想把Canvas动画直接导出为标准MP4,这事儿其实没想象中那么复杂,但有个关键点得搞清楚——你不能指望ffmpeg.wasm直接“绑定”Canvas对象去逐帧抓图。它毕竟是在浏览器里跑的WebAssembly版本,不是原生FFmpeg,没法像管道一样持续往里灌帧。所以正确的做法分两步走:先用浏览器自带的API把Canvas流录下来,然后再交给ffmpeg.wasm去转格式。
推荐方案:MediaRecorder + ffmpeg.wasm
第一步:用 MediaRecorder 把 Canvas 录成 WebM
MediaRecorder 这个API现在主流的现代浏览器基本都支持,它可以直接从Canvas元素上拿到一个MediaStream,然后录制成.webm文件(VP8/VP9编码),不需要任何额外依赖,兼容性也不错。
直接上代码:
const canvas = document.getElementById('myCanvas');
const stream = canvas.captureStream(60); // 60 FPS(根据动画实际帧率调整)
const mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm' });
const chunks = [];
mediaRecorder.ondataa vailable = (e) => chunks.push(e.data);
mediaRecorder.onstop = () => {
const blob = new Blob(chunks, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'animation.webm';
a.click();
};
// 开始录制(例如点击按钮触发)
function startRecording() {
chunks.length = 0;
mediaRecorder.start();
}
function stopRecording() {
mediaRecorder.stop();
}
这里有个坑必须提一下:canvas.captureStream()在比较老版本的Safari里不支持。如果你需要全平台兼容,那就得用OffscreenCanvas配合ImageBitmap和RTCPeerConnection去模拟流,那套方案复杂不少。不过话说回来,现在绝大多数桌面和移动端的现代浏览器都已经原生支持了,实际用起来问题不大。
第二步:用 ffmpeg.wasm 把 WebM 转成 MP4(H.264/AAC)
.webm文件在多数播放器里都能放,但有些场景——比如微信内嵌浏览器、插入PPT、导入剪辑软件——还是MP4更稳。这时候就该ffmpeg.wasm登场了,全程不需要后端参与。
import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg';
const ffmpeg = createFFmpeg({
log: true,
corePath: 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/ffmpeg-core.js',
});
async function transcodeToMP4(webmBlob) {
await ffmpeg.load(); // 确保 FFmpeg 初始化完成
// 将 Blob 转为 Uint8Array 并写入 FFmpeg FS
const arrayBuffer = await webmBlob.arrayBuffer();
ffmpeg.FS('writeFile', 'input.webm', new Uint8Array(arrayBuffer));
// 执行转码命令(关键参数说明):
// -c:v libx264 → 使用 H.264 编码器(MP4 标准)
// -crf 23 → 控制质量(18~28,值越小质量越高)
// -preset fast → 平衡速度与压缩率
// -c:a aac → 添加 AAC 音频编码(即使无声也建议保留音频轨道以确保 MP4 兼容性)
await ffmpeg.run(
'-i', 'input.webm',
'-c:v', 'libx264',
'-crf', '23',
'-preset', 'fast',
'-c:a', 'aac',
'-y', 'output.mp4'
);
// 读取输出文件并生成下载链接
const data = ffmpeg.FS('readFile', 'output.mp4');
const mp4Blob = new Blob([data.buffer], { type: 'video/mp4' });
const url = URL.createObjectURL(mp4Blob);
const a = document.createElement('a');
a.href = url;
a.download = 'animation.mp4';
a.click();
}
小提示:ffmpeg.wasm第一次加载比较慢,大概20到40MB的样子,建议提前预加载,或者加个loading状态提示,免得用户干等。生产环境里推荐用@ffmpeg/ffmpeg的load()配置corePath走CDN加速。
关键注意事项与优化建议
- 帧率一致性:
canvas.captureStream(fps)的fps参数最好和你的requestAnimationFrame实际渲染帧率匹配上。可以用performance.now()统计平均帧间隔来校准,否则可能出现音画不同步。 - 内存控制:录的时间长了,
chunks数组会占不少内存。建议限制单次录制时长,比如别超过30秒。如果需求更复杂,可以研究下mediaRecorder.ondataa vailable的流式分片处理。 - 无音频处理:如果你的Canvas动画本身就是无声的,可以用
-an参数禁用音频。但问题来了——有些设备或播放器对纯视频的MP4支持不太好,所以还是推荐保留-c:a aac -f mp4,生成一个带静音AAC轨道的文件,兼容性更稳。 - 性能权衡:ffmpeg.wasm转码时CPU占用高得离谱,强烈建议放到Web Worker里去跑,别堵死主线程的UI。
总结下来就是:别想着Canvas直接连FFmpeg。正确的姿势是先用浏览器标准能力(MediaRecorder)高效地把画面采下来,然后让ffmpeg.wasm去做专业的格式转换。两者配合,完全可以在零后端依赖的情况下,把任意Canvas动画可靠地导出成通用MP4文件。
