10、Canvas捕获:captureStream()方法、捕获Canvas动画、实现画板录制功能
Canvas 捕获,说白了就是把画布上的每一帧画面变成视频流。
我最早接触这个功能,是在做一个在线白板项目的时候。用户画了半天,突然问了一句:「老师,我这个画图过程能录下来吗?」当时我第一反应是用屏幕录制,但一想,那得多占资源啊。后来翻到 captureStream() 这个方法,才发现——原来 Canvas 自己就能输出视频流。
10.1 captureStream() 方法:Canvas 的“摄像头”
captureStream() 是 HTMLCanvasElement 上的一个方法。调用它,就能把 Canvas 的内容实时转成一个 MediaStream 对象。这个流,跟摄像头采集的流一模一样,可以直接喂给 MediaRecorder 去录制。
基本用法很简单:
const canvas = document.getElementById('myCanvas');
const stream = canvas.captureStream(30); // 30 FPS
参数是帧率,默认是 60。我个人习惯设成 30,因为录画板动画,30 帧已经足够流畅,而且文件体积小很多。
10.2 捕获 Canvas 动画:从静态到动态
Canvas 动画,说白了就是不断重绘。每次 requestAnimationFrame 回调里,你清空画布、画新图形。captureStream() 会自动捕捉每一次绘制的结果。
我举个例子。假设你有一个小球在 Canvas 上弹跳:
const canvas = document.getElementById('bounceCanvas');
const ctx = canvas.getContext('2d');
const stream = canvas.captureStream(30);
let x = 50, y = 50, dx = 2, dy = 2;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(x, y, 20, 0, Math.PI * 2);
ctx.fillStyle = 'blue';
ctx.fill();
x += dx;
y += dy;
if (x > 280 || x < 20) dx = -dx;
if (y > 280 || y < 20) dy = -dy;
requestAnimationFrame(draw);
}
draw();
这时候,stream 里就已经包含了每一帧的动画画面。你什么都不用额外做,captureStream() 会自动同步。
ctx.save() 和 ctx.restore(),记得确保每次绘制完成后状态是干净的。我曾经遇到过一个问题:画板录制到一半,突然画面变黑了。查了半天,发现是 save/restore 没配对,导致 Canvas 状态栈溢出。
10.3 实现画板录制功能:完整流程
画板录制,核心就三步:
- 用户画画时,更新 Canvas 内容。
- 用 captureStream() 获取 Canvas 的视频流。
- 用 MediaRecorder 把流录下来,保存成文件。
下面是一个完整的实现思路:
const canvas = document.getElementById('drawCanvas');
const ctx = canvas.getContext('2d');
const stream = canvas.captureStream(30);
const recorder = new MediaRecorder(stream, { mimeType: 'video/webm' });
let chunks = [];
recorder.ondataavailable = (e) => {
if (e.data.size > 0) {
chunks.push(e.data);
}
};
recorder.onstop = () => {
const blob = new Blob(chunks, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'drawing.webm';
a.click();
URL.revokeObjectURL(url);
chunks = [];
};
// 开始录制
document.getElementById('startBtn').onclick = () => {
recorder.start();
};
// 停止录制
document.getElementById('stopBtn').onclick = () => {
recorder.stop();
};
这里有个坑,我踩过好几次:MediaRecorder 的 mimeType 不一定所有浏览器都支持。建议先检测一下:
const mimeType = MediaRecorder.isTypeSupported('video/webm;codecs=vp9')
? 'video/webm;codecs=vp9'
: 'video/webm';
10.4 画板录制的进阶技巧
实际项目中,画板录制往往不只是录画面。你可能还需要:
- 录制鼠标轨迹:把鼠标位置画成一个小圆点,方便回看时知道笔触走向。
- 录制操作时间轴:配合
performance.now()记录每笔的起始时间,方便后期做回放。 - 限制录制时长:防止用户忘记停止,导致文件过大。我一般设个 5 分钟上限。
举个例子,限制录制时长:
let maxDuration = 5 * 60 * 1000; // 5分钟
let timer;
document.getElementById('startBtn').onclick = () => {
recorder.start();
timer = setTimeout(() => {
if (recorder.state === 'recording') {
recorder.stop();
alert('录制已自动停止(5分钟上限)');
}
}, maxDuration);
};
10.5 知识体系图
下面这张图,帮你理清 Canvas 捕获的核心逻辑:
10.6 常见问题与避坑
| 问题 | 原因 | 解决办法 |
|---|---|---|
| 录出来的视频是黑屏 | Canvas 没有正确绘制,或者 captureStream() 调用时机太早 | 确保在动画循环开始后再调用 captureStream() |
| 视频帧率很低 | Canvas 绘制本身太慢,或者帧率参数设置过高 | 降低 captureStream() 的帧率参数,比如设成 15 或 30 |
| 录制文件太大 | 没有限制时长,或者分辨率太高 | 设置最大录制时长,或者缩小 Canvas 尺寸 |
| 浏览器不支持录制格式 | MediaRecorder 的 mimeType 不兼容 | 用 isTypeSupported() 检测并降级 |
嗯,画板录制这个功能,说起来简单,但真正做好其实有不少细节。我建议你动手写一个 demo,从最简单的画线开始,慢慢加上颜色、粗细、撤销功能,最后再套上录制。这样一步步来,理解会深很多。