深入浅出:Canvas 高性能渲染实践与内存优化

发布于 2026年4月24日 · 分类:前端工程化 · 阅读约 12 分钟

在图像与可视化场景里,DOM 节点数量一旦上来,样式计算和布局会迅速占满主线程。Canvas 的核心价值是把“很多对象”统一为“位图渲染”,使性能模型更可控。

但 Canvas 也会踩坑:高频 getImageData、全量重绘、临时对象爆炸,都会造成严重掉帧。本文从实战角度总结一套可复用的优化流程。

1. 先做性能测量

先打开 Performance 面板,观察单帧里 ScriptingRenderingPainting 的占比。目标是把业务脚本控制在 6~8ms,给浏览器留出合成和输入处理空间。

16.7ms 是 60fps 的硬预算。不要在没测量前直接“优化代码风格”。

2. 脏矩形 + 分层渲染

静态背景缓存成一层,动态对象一层,避免每帧全屏 clear + 全量重画。

const dirty = [];
function mark(x, y, w, h) { dirty.push({ x, y, w, h }); }
function render(ctx) {
  for (const r of dirty) {
    ctx.clearRect(r.x, r.y, r.w, r.h);
    drawDynamic(ctx, r);
  }
  dirty.length = 0;
}

3. OffscreenCanvas 放到 Worker

重计算迁移到 Worker,主线程只接收渲染结果,能显著减少输入延迟与卡顿感。

// main
const off = canvas.transferControlToOffscreen();
worker.postMessage({ canvas: off }, [off]);
// worker
self.onmessage = (e) => {
  const ctx = e.data.canvas.getContext('2d');
  tick(ctx);
};

4. ImageData 的内存成本

getImageData/putImageData 本质上有数据复制成本。建议一次分配,循环复用缓冲区,减少 GC 高频回收。

const img = ctx.createImageData(width, height);
const d = img.data;
for (let i = 0; i < d.length; i += 4) {
  d[i] = 34; d[i + 1] = 197; d[i + 2] = 94; d[i + 3] = 255;
}
ctx.putImageData(img, 0, 0);

5. 对象池与输入解耦

  • 粒子、临时对象走对象池,避免热点路径 new
  • 输入事件只更新状态,绘制统一在 requestAnimationFrame 消费。
  • 页面进入后台时暂停动画,回前台再恢复。

把这套方法用在生产项目后,我们的渲染模块在中端安卓机上从 28~35fps 稳定到 55~60fps,峰值内存下降约 30%。

首页 · 最新文章