Canvas动画本质是requestAnimationFrame循环重绘,需正确清屏、同步刷新、避免耗时操作,并注意图片加载、坐标转换与移动端适配。

canvas 动画本质是 requestAnimationFrame 循环重绘
Canvas 本身不支持“动画”概念,所谓动画,就是反复清空画布、重新绘制新位置图形的过程。核心不是 drawImage 或 fillRect 多酷,而是用 requestAnimationFrame 控制节奏,让它按屏幕刷新率(通常 60fps)稳定执行。
常见错误是用 setTimeout 或 setInterval 驱动,结果卡顿、掉帧、不同设备表现不一致。因为它们不和屏幕刷新同步,还可能被浏览器节流。
-
requestAnimationFrame必须在每次绘制末尾递归调用,否则只跑一帧 - 不要在动画循环里做耗时计算(比如遍历大数组、解析 JSON),会直接拖慢帧率
- 如果只是位移/缩放/旋转,优先用
ctx.save()+ctx.translate()+ctx.restore(),比重绘整个图形快
clearRect 不清全画布就留残影
动画中图形移动后,旧位置的像素不会自动消失。必须显式清除——但很多人只清局部区域,或清错坐标,导致拖尾、重影、边界错位。
典型错误:ctx.clearRect(0, 0, 100, 100) 只清左上小块,而画布是 800×600,其余残留。
立即学习“前端免费学习笔记(深入)”;
- 最稳妥做法:
ctx.clearRect(0, 0, canvas.width, canvas.height) - 想优化性能可只清“脏区域”,即上一帧图形所在矩形 + 当前帧新位置矩形的并集,但需自己算
left、top、width、height - 用
canvas.width = canvas.width也能清屏(重设宽高会重置上下文),但会丢失strokeStyle、lineWidth等状态,不推荐
图像动画要注意 drawImage 的坐标系和加载时机
用 drawImage 动画图片时,90% 的“不动”“错位”“黑屏”问题都出在:图片还没加载完就调用了绘制,或传错了 sx/sy(源裁剪坐标)。
错误现象:Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The image argument is a canvas element with a width or height of 0.
- 确保图片
onload触发后再启动动画循环,或用img.complete判断 -
drawImage(img, dx, dy)是贴图到画布;drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh)才是裁剪+缩放+贴图,别漏参数 - 如果做雪碧图动画,
sx要随帧号动态算,比如每帧宽 64px:sx = frameIndex * 64
移动端 touchmove 动画容易掉帧和偏移
在手机上用 touchmove 驱动 canvas 动画(比如手指拖拽图形),常遇到响应迟滞、轨迹跳变、坐标不准。根本原因是事件频率过高、未节流,且 touches[0].clientX 没转成 canvas 坐标系。
错误现象:手指划过,图形只在几个点上跳跃出现,或始终偏右 20px。
- 必须把屏幕坐标转为 canvas 坐标:
const rect = canvas.getBoundingClientRect(); const x = e.touches[0].clientX - rect.left; - 给
touchmove加{ passive: false }并调用e.preventDefault(),否则 iOS Safari 会拦截默认行为导致卡顿 - 不要在
touchmove里直接重绘,应只更新数据(如currentX),让requestAnimationFrame循环统一绘制
canvas 动画真正难的不是画什么,而是每一帧里:清得对不对、算得准不准、更新得及时不冲突。尤其是多对象、带物理逻辑、还要兼容老安卓 WebView 的场景,requestAnimationFrame 的调用时机和 clearRect 的范围,稍不注意就满屏残影。











