初学requestAnimationFrame:Canvas圆形点击动效实现后的困惑
嘿,我完全懂你现在的困惑——刚摸requestAnimationFrame和Canvas的时候,很容易被它们的底层逻辑打个措手不及!你意外实现了动效但摸不清原理,核心问题其实出在Canvas的绘制模式和requestAnimationFrame的帧循环逻辑上,咱一步步拆解:
1. Canvas是「即时模式」,不是你以为的「保留模式」
你原本觉得画完一个静止圆后,再画新圆会叠加在上面、原圆不动?但Canvas本质就是一块“电子画布”——每次调用ctx.arc()、ctx.fill()这些绘制API时,都是在当前画布上重新画像素,而不是像DOM那样往页面里加新元素。
如果你的代码里没在每一帧清屏(比如ctx.clearRect(0,0,canvas.width,canvas.height)),新画的圆会和旧圆重叠留轨迹;但如果你的动效逻辑是修改同一个圆的坐标再重绘,那看起来就是原圆在动,而非新增了一个圆。
2. requestAnimationFrame的「帧循环」是怎么工作的
当你调用requestAnimationFrame时,浏览器会把它塞进下一帧的渲染队列。每一次回调执行,都是在为当前帧的画面做准备:
- 如果你点击按钮后启动的循环是「更新坐标→重绘圆」,那每一帧都会用新坐标覆盖旧画面,看起来就是这个圆在动;
- 要是想实现「原圆静止,新圆运动」,你得把所有圆的状态存起来,每一帧先清屏,再依次绘制所有圆——先画静止的原圆,再画更新后的运动圆。
用示例代码帮你搞懂
先模拟你“意外实现”的代码逻辑(大概率是这样):
const canvas = document.getElementById('myCanvas'); const ctx = canvas.getContext('2d'); let x = 50; // 圆的初始x坐标 let y = 50; // 先画静止圆 ctx.beginPath(); ctx.arc(x, y, 20, 0, Math.PI*2); ctx.fillStyle = 'red'; ctx.fill(); document.getElementById('btn').addEventListener('click', () => { function animate() { // 清屏后重绘,所以看不到之前的静止圆 ctx.clearRect(0, 0, canvas.width, canvas.height); x += 1; ctx.beginPath(); ctx.arc(x, y, 20, 0, Math.PI*2); ctx.fillStyle = 'red'; ctx.fill(); requestAnimationFrame(animate); } animate(); });
这个代码里,点击按钮后帧循环会清屏→更新坐标→重绘圆,所以看起来是原来的圆在动,而非新增圆。
如果要实现你最初的预期(原圆静止,新圆运动),得改成这样:
const canvas = document.getElementById('myCanvas'); const ctx = canvas.getContext('2d'); // 用数组保存所有圆的状态 const circles = [ { x: 50, y: 50, color: 'red', isMoving: false }, // 静止的原圆 ]; // 初始绘制静止圆 function drawCircles() { ctx.clearRect(0, 0, canvas.width, canvas.height); circles.forEach(circle => { ctx.beginPath(); ctx.arc(circle.x, circle.y, 20, 0, Math.PI*2); ctx.fillStyle = circle.color; ctx.fill(); }); } drawCircles(); document.getElementById('btn').addEventListener('click', () => { // 新增一个运动的圆 circles.push({ x: 50, y: 50, color: 'blue', isMoving: true, speed: 1 }); function animate() { // 只更新运动圆的坐标 circles.forEach(circle => { if (circle.isMoving) { circle.x += circle.speed; } }); // 重新绘制所有圆 drawCircles(); requestAnimationFrame(animate); } animate(); });
现在点击按钮后,蓝色新圆会向右运动,红色原圆则保持静止——这就是你最初想要的效果。
总结你的「意外实现」原因
你之前的代码大概率是:
- 没有保存多个圆的状态,只修改了同一个圆的坐标;
- 帧循环里重绘时清了屏,所以看不到之前的静止圆;
- 最终看起来是原圆在动,而非新增圆。
核心记住这两点:Canvas不会帮你保留旧画面,所有内容都要你在每一帧手动重绘;requestAnimationFrame的作用是让你跟着浏览器的渲染节奏,连续更新并绘制画面,形成流畅动画。
内容的提问来源于stack exchange,提问作者Don199

