核心是将渲染循环完全移至web worker+offscreencanvas:①主线程移交canvas控制权;②worker获取上下文并渲染;③用transfertoimagebitmap零拷贝上屏;④控制尺寸、分离图层、迁移物理计算,确保60fps不掉帧。

核心就一条:把整个渲染循环从主线程完整剥离,交给 Web Worker + OffscreenCanvas 承担。主线程只管交互和调度,不碰任何像素计算。
必须完成的三步移交
这是不可跳过的初始化链路,缺一不可:
- 主线程调用 canvas.transferControlToOffscreen(),把 HTMLCanvasElement 的控制权彻底交出——移交后,主线程再调用 getContext() 会直接报错
- 将返回的 OffscreenCanvas 对象通过 postMessage(canvas, [canvas]) 发送给 Worker,第二个参数是 transfer list,确保零拷贝
- Worker 内用 offscreen.getContext('2d') 或 getContext('webgl') 获取上下文,从此所有 drawImage、fillRect、shader 编译等操作都在子线程执行
帧提交必须用 transferToImageBitmap
Worker 渲染完一帧,不能用 toDataURL 或 getImageData 返回数据——那会触发大内存复制并阻塞 Worker。正确做法是:
- 调用 offscreen.transferToImageBitmap(),生成一个可转移的 ImageBitmap 对象
- 通过 postMessage(bitmap, [bitmap]) 发回主线程,仍走转移语义
- 主线程用 canvas.getContext('bitmaprenderer').transferFromImageBitmap(bitmap) 直接上屏,毫秒级合成,无解码、无缩放、无拷贝
防崩溃的关键尺寸控制
高分辨率设备(如 iPhone DPR=3)下,4000×3000 原图会生成超 1 亿像素纹理,极易触发浏览器闪退。必须主动降级:
- 在 Worker 中计算目标尺寸:Math.min(width * dpr, 4096) 和 Math.min(height * dpr, 4096)
- 创建 OffscreenCanvas 时严格按安全尺寸申请,不依赖设备原生 DPR 全量放大
- 对粒子动画等动态场景,可进一步分离静态层(背景)与动态层(弹幕/粒子),仅重绘变化部分
物理与渲染逻辑全放 Worker
不要只搬绘图,把耗时计算也一并移入:
- 粒子位置更新、碰撞检测、贝塞尔插值等 CPU 密集型逻辑,全部在 Worker 中运行
- 主线程仅发送用户事件(如鼠标坐标、播放/暂停指令),不做状态同步
- Worker 内使用 requestAnimationFrame 或自定时器驱动渲染循环,与主线程完全解耦
做到这四点,UI 线程不再被渲染抢占,60fps 可稳定维持,即使渲染 10 万粒子或高频弹幕也不会丢帧。











