Canvas 动画循环的学习笔记(requestAnimationFrame)
想做会动的画面,一开始我想当然地用 setInterval 每隔几十毫秒画一次,结果画面一顿一顿的。查了资料才知道,浏览器专门为动画提供了一个方法:requestAnimationFrame。
最简单的动画循环
function draw() {
// 1. 清屏:把上一帧的画面擦掉
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 2. 画这一帧的内容
ctx.fillRect(x, 100, 20, 20);
// 3. 改一下位置
x += 2;
// 4. 请求浏览器下一次刷新的时候再画
requestAnimationFrame(draw);
}
draw();
它和 setInterval 最大的区别是:它跟着屏幕的刷新节奏走(一般每秒 60 次),而且浏览器标签页切到后台时会自动暂停,不浪费电。
清屏很重要
我一开始忘了清屏,方块就变成了拖着长尾巴的一条线——因为上一帧画的还在。每帧开头用 clearRect 把画布擦干净,画面就干净了。
deltaTime 是干嘛的
不同设备的刷新率不一样,有的屏幕一秒 60 帧,有的是 120 帧。如果每帧固定加 2 像素,在 120 帧的屏幕上速度就快了一倍。解决办法是用两帧之间的真实时间差:
let lastTime = 0;
function draw(now) {
// now 是浏览器传进来的当前时间(毫秒)
const deltaTime = now - lastTime;
lastTime = now;
// 按 60 帧为基准折算,速度快慢就和设备无关了
const speed = 2 * (deltaTime / (1000 / 60));
x += speed;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(x, 100, 20, 20);
requestAnimationFrame(draw);
}
理解了这个以后再看别人的游戏代码,那些 dt 参数一下子就懂了。
小结
这一篇是我觉得收获最大的一课。小练习里的星空动画就是照这个思路写的:一堆小星星,每帧移动一点点,点击的时候加一颗流星。