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 帧已经足够流畅,而且文件体积小很多。

小提示: captureStream() 返回的流,只包含一轨 video。如果你需要音频,得自己用 getUserMedia 或者 AudioContext 去合成。

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 实现画板录制功能:完整流程

画板录制,核心就三步:

  1. 用户画画时,更新 Canvas 内容。
  2. 用 captureStream() 获取 Canvas 的视频流。
  3. 用 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();
};

这里有个坑,我踩过好几次:MediaRecordermimeType 不一定所有浏览器都支持。建议先检测一下:

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 捕获的核心逻辑:

Canvas 捕获核心流程 用户绘制操作 Canvas 画布 captureStream(30) MediaStream MediaRecorder 录制 & 保存 输出 WebM 文件 下载 / 分享 应用场景:在线白板 / 教学录制 / 协作绘图

10.6 常见问题与避坑

问题 原因 解决办法
录出来的视频是黑屏 Canvas 没有正确绘制,或者 captureStream() 调用时机太早 确保在动画循环开始后再调用 captureStream()
视频帧率很低 Canvas 绘制本身太慢,或者帧率参数设置过高 降低 captureStream() 的帧率参数,比如设成 15 或 30
录制文件太大 没有限制时长,或者分辨率太高 设置最大录制时长,或者缩小 Canvas 尺寸
浏览器不支持录制格式 MediaRecorder 的 mimeType 不兼容 用 isTypeSupported() 检测并降级
核心总结: Canvas 捕获的本质,就是把画布变成视频源。captureStream() 是桥梁,MediaRecorder 是录像机。你只需要关注画布上的绘制逻辑,剩下的交给浏览器。

嗯,画板录制这个功能,说起来简单,但真正做好其实有不少细节。我建议你动手写一个 demo,从最简单的画线开始,慢慢加上颜色、粗细、撤销功能,最后再套上录制。这样一步步来,理解会深很多。

公众号:蓝海资料掘金营,微信deep3321