跳到主要内容

Context生命周期与读回

HTMLCanvasElement.getContext("2d", options) 第一次成功之后:

  • 以后用同一 contextType 再调,返回 同一个对象
  • 不能改成 webgl / bitmaprenderer;再要会返回 null
  • options 只在第一次生效。事后再传 { willReadFrequently: true } 不会把 GPU 表面改成软件表面。

1. 创建时就要做的架构选择

interface SurfaceOptions {
/** 真的会 getImageData / 像素级拾取 / 滤镜读回 */
readback: boolean;
/** 低延迟绘制(部分引擎),可能与合成器解耦,代价是读回更不稳定 */
desynchronized?: boolean;
alpha?: boolean;
colorSpace?: "srgb" | "display-p3";
}

export function create2dContext(
canvas: HTMLCanvasElement,
options: SurfaceOptions,
): CanvasRenderingContext2D {
const ctx = canvas.getContext("2d", {
alpha: options.alpha ?? true,
desynchronized: options.desynchronized ?? false,
colorSpace: options.colorSpace ?? "srgb",
willReadFrequently: options.readback,
});
if (!ctx) {
throw new Error("failed to create 2d context");
}
return ctx;
}
hint适用不适用
willReadFrequently: true每帧或高频 getImageData、像素级碰撞、把 canvas 当图像处理缓冲纯绘制动画、粒子、要 GPU 合成
desynchronized: true手写/笔迹、延迟敏感需要稳定读回、需要和 DOM 严格同步的一帧
alpha: false不透明游戏视口,省混合圆角、叠加在页面上的 HUD

getContextAttributes() 可以确认引擎实际采纳了哪些 hint。不要假设 desynchronized 一定生效。

2. 读回的真实成本

getImageData / putImageData 会迫使引擎把 GPU 纹理 readback 到 CPU。未声明 willReadFrequently 时,这一下经常是一帧里最大的尖刺。声明之后,引擎倾向整张 2D canvas 走软件光栅,所有绘制变慢,读回变稳

所以这是分叉,不是开关:

  • 绘制型 surface:禁止热路径读回;拾取走几何(isPointInPath / 场景图)。
  • 处理型 surface:从一开始就 willReadFrequently: true,接受 CPU 路径。

不要「平时 GPU,偶尔读一张全画布做模糊」。那是最差组合。

3. 多 context 的正确拆法

需要又绘制又读回时,拆 两张 canvas,不要拆两次 getContext

const view = create2dContext(viewCanvas, { readback: false });
const work = create2dContext(workCanvas, { readback: true });

// 可见层只 drawImage 工作层的结果
view.drawImage(workCanvas, 0, 0);

drawImage(HTMLCanvasElement) 是 GPU 友好的提交方式;把工作层的像素读出来再 putImageData 到可见层是倒退。

4. 失败形态

症状原因
getContext("webgl")null已经拿过 2d
声明了 readback 仍然卡读的是另一张未声明的 canvas,或读全画布
透明变黑底alpha: false 或 JPEG 导出

权威资料

核对日期:2026-08-26