OffscreenCanvas本身不丝滑,关键在于将全部绘图逻辑移入Web Worker并避开主线程同步操作;需主线程创建canvas、transferControlToOffscreen后移交,Worker内禁用读像素、DOM访问和动态加载资源,严格处理DPR与尺寸限制,并通过transferToImageBitmap零拷贝回传位图。
offscreencanvas 本身不“丝滑”,它只是一条跨线程绘图的通道;真正实现丝滑的关键,是把全部绘图逻辑完整移入 web worker,并严格避开任何会触发主线程同步的操作。
必须由主线程移交控制权
Worker 中不能 new OffscreenCanvas()——这会直接报 TypeError: Illegal constructor。正确做法是:
- 主线程准备一个
<canvas id="render"></canvas>(可不插入 DOM) - 调用
canvas.transferControlToOffscreen()获取离屏实例 - 通过
worker.postMessage({ offscreen }, [offscreen])发送,且 必须把 offscreen 放进 transfer 列表 - 移交后,原 canvas 的
getContext()会抛SecurityError,确保无意外回退
Worker 内只能做纯绘制,禁用三类操作
以下任一行为都会让渲染瞬间切回主线程,导致卡顿甚至白屏:
-
读像素:禁用
getImageData()、toDataURL()、toBlob() -
碰 DOM 或全局对象:不能访问
document、window、localStorage,也不能new Image() -
动态加载资源:字体未预加载时设
ctx.font会静默失败;图片必须由主线程转成ImageBitmap后传入
尺寸与 DPR 必须主动算清楚
高 DPR 下画布像素超限(如 >4096×4096)会导致 iOS Safari 或 Android WebView 崩溃:
- 用
canvas.getBoundingClientRect()获取显示宽高,别用window.innerWidth - 安全宽度 =
Math.min(4096, Math.floor(rect.width * devicePixelRatio)) - 若超限,降级 DPR:
Math.max(1, Math.floor(4096 / rect.width)),宁可模糊也不闪退 - DPR 值必须由主线程计算好传入 Worker,Worker 内无法读
window.devicePixelRatio
结果回传要轻量,避免高频截帧
主线程只需极轻量地合成画面,不要在每帧都生成新位图:
- Worker 绘制完成后调
offscreen.transferToImageBitmap()(该操作清空原画布) - 用
postMessage(bitmap, [bitmap])零拷贝传回 - 主线程用
ctx.drawImage(bitmap, 0, 0)直接绘制,不触发重排重绘 -
transferToImageBitmap()是异步耗时操作,建议每 2–3 帧截一次,或仅在内容变化时触发
不复杂但容易忽略。










