前两天帮朋友调一个实时波形图的页面,canvas 一画多条线就掉帧,拖动鼠标都像在看幻灯片。他以为是数据太大,其实问题出在 canvas 自身的绘制逻辑上——很多开发者只顾着画,忘了 canvas 是个“单线程画家”,画得慢、清得乱、算得多,它就累。
别急着 clearRect,先想想要不要清
常见误区:每次重绘前必调 ctx.clearRect(0, 0, width, height)。但如果只是局部更新(比如只动一条折线),全屏擦除反而浪费。试试只擦需要更新的矩形区域:
ctx.clearRect(x - 5, y - 5, 10, 10); // 只擦光标附近小块更进一步,如果背景不变、只有前景动,干脆跳过清屏,用新图形直接覆盖旧位置——前提是图形不透明、无重叠干扰。
drawImage 比手动画快得多
有次做粒子动画,每帧循环 200 个圆点,用 arc + fill 死撑,60fps 直接掉到 25。换成预渲染成小 canvas,再批量 drawImage 贴图,帧率立马回血:
// 预先画好粒子贴图(只需一次)
const particleCanvas = document.createElement('canvas');
particleCanvas.width = 4;
particleCanvas.height = 4;
const pCtx = particleCanvas.getContext('2d');
pCtx.fillStyle = '#ff6b6b';
pCtx.beginPath();
pCtx.arc(2, 2, 2, 0, Math.PI * 2);
pCtx.fill();
// 渲染时直接贴
particles.forEach(p => {
ctx.drawImage(particleCanvas, p.x, p.y);
});本质是把“CPU 算路径 + 填色”换成“GPU 贴图”,浏览器底层做了大量优化。
离屏 canvas:给重活分个车间
复杂图表要叠加网格、坐标轴、多层曲线?别全挤在主 canvas 上画。用离屏 canvas 分层渲染:
const offscreen = document.createElement('canvas');
offscreen.width = width;
offscreen.height = height;
const offCtx = offscreen.getContext('2d');
// 先画不变的背景层(网格、坐标轴)
offCtx.strokeStyle = '#eee';
// ...画网格代码
// 主 canvas 只负责动态内容
ctx.clearRect(0, 0, width, height);
ctx.drawImage(offscreen, 0, 0); // 背景一次贴入
ctx.stroke(curvePath); // 再画变动的曲线这样,网格不用每帧重画,CPU 负担一下轻了一半。
requestAnimationFrame 别乱套 try-catch
有人为了防错,在 rAF 回调里包一层 try-catch,结果发现帧率不稳。因为异常捕获本身有开销,而 canvas 绘制本就该是“确定性流程”。真要容错,提前校验数据:
function render() {
if (!Array.isArray(data) || data.length === 0) return; // 快速退出
ctx.clearRect(0, 0, w, h);
drawLines(ctx, data);
requestAnimationFrame(render);
}数据进绘制前就过滤掉 NaN、Infinity 或越界坐标,比运行中抓异常干净利落。
canvas 不是黑盒,它是你手里的画笔,也是你调度的工人。画得少一点、画得巧一点、让浏览器少猜一点——卡顿自然就散了。