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