Canvas 动画:让你的像素舞动起来,告别卡顿时代
想象一下,你正在用 Canvas 画一个太阳,你想让它缓缓升起,光芒四射。如果你直接用 setInterval
或者 setTimeout
来控制它的位置,你会发现太阳升起的过程断断续续,就像得了帕金森一样,完全没有那种丝滑的日出感。
这感觉是不是很糟糕?就像你精心准备了一场浪漫的求婚,结果背景音乐卡碟,气氛全无。
别担心,今天我们就来聊聊 Canvas 动画的秘密武器——requestAnimationFrame
,它可以让你告别卡顿,让你的像素们跳起流畅的华尔兹。
为什么传统的定时器会让你“卡成翔”?
在深入 requestAnimationFrame
之前,我们先来了解一下为什么 setInterval
和 setTimeout
在动画方面表现得如此糟糕。
简单来说,它们的问题在于“不够懂浏览器的心”。
-
刷新频率不一致: 浏览器会定期刷新屏幕,这个刷新频率通常是 60Hz,也就是每秒刷新 60 次。而
setInterval
和setTimeout
只能按照你设定的时间间隔执行,它们无法精确地与浏览器的刷新频率同步。这意味着,你的动画更新可能发生在浏览器刷新之前、之后或者正好在刷新的时候,这会导致画面撕裂、抖动等问题。想象一下,你是一名舞蹈指挥,你让舞者每 10 毫秒跳一下,但是舞台的灯光每 16.6 毫秒闪一下。舞者的动作和灯光不同步,结果就是一场混乱的表演。
-
CPU 负担重: 即使你设置的定时器间隔很短,浏览器也不一定会按照你的设定来执行。当浏览器忙于其他任务时,它可能会延迟定时器的执行。更糟糕的是,即使浏览器空闲,它也可能会浪费大量的 CPU 资源来执行不必要的动画更新,因为有些更新可能发生在屏幕刷新之间,用户根本看不到。
这就像你雇了一个园丁,让他每分钟修剪一次草坪,即使草坪根本没长高。这不仅浪费了钱,还可能把草坪修剪得坑坑洼洼。
-
标签页不可见时的浪费: 当你的 Canvas 动画在一个不可见的标签页中运行时,
setInterval
和setTimeout
仍然会继续执行,这会浪费大量的 CPU 资源和电量。这就好比你家里装了一个自动浇花系统,即使你不在家,它仍然会按照设定的时间浇花,这不仅浪费了水,还可能把你的花淹死。
requestAnimationFrame
:浏览器的好伙伴,动画的真爱
requestAnimationFrame
就像是浏览器专门为动画定制的 API。它告诉浏览器:“嘿,我有一个动画需要更新,请在下一次屏幕刷新之前执行我的更新函数。”
requestAnimationFrame
的优势在于:
-
与浏览器刷新同步:
requestAnimationFrame
会自动与浏览器的刷新频率同步,确保你的动画更新发生在屏幕刷新之前,从而避免画面撕裂和抖动。这就像你让舞蹈指挥和灯光师一起工作,确保舞者的动作和灯光完美同步,呈现出一场精彩的表演。
-
节省 CPU 资源:
requestAnimationFrame
只会在屏幕需要刷新时才执行更新函数,从而节省了大量的 CPU 资源。这就像你雇了一个园丁,让他只在草坪长高时才修剪,这样既能保持草坪的美观,又能节省开支。
-
标签页不可见时暂停: 当你的 Canvas 动画在一个不可见的标签页中运行时,
requestAnimationFrame
会自动暂停执行,从而节省 CPU 资源和电量。这就像你家里装了一个智能浇花系统,它只有在你需要的时候才会浇花,这样既能保证花卉的生长,又能节省水资源。
如何使用 requestAnimationFrame
?
使用 requestAnimationFrame
非常简单,只需要三步:
-
定义你的动画更新函数: 这个函数负责更新 Canvas 上的元素的位置、大小、颜色等属性。
-
使用
requestAnimationFrame
请求下一次动画更新: 在你的动画更新函数中,使用requestAnimationFrame
再次请求下一次动画更新。 -
启动动画: 调用
requestAnimationFrame
启动动画。
下面是一个简单的例子,演示如何使用 requestAnimationFrame
让一个圆圈在 Canvas 上移动:
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
let x = 0;
const radius = 20;
const speed = 2;
function drawCircle() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // 清空画布
ctx.beginPath();
ctx.arc(x, canvas.height / 2, radius, 0, 2 * Math.PI);
ctx.fillStyle = 'red';
ctx.fill();
x += speed;
if (x > canvas.width + radius) {
x = -radius;
}
requestAnimationFrame(drawCircle); // 请求下一次动画更新
}
requestAnimationFrame(drawCircle); // 启动动画
在这个例子中,drawCircle
函数负责绘制圆圈并更新它的位置。requestAnimationFrame(drawCircle)
会告诉浏览器在下一次屏幕刷新之前执行 drawCircle
函数。这样,圆圈就会以流畅的速度在 Canvas 上移动。
requestAnimationFrame
的一些小技巧
-
使用时间戳来计算动画进度:
requestAnimationFrame
会传递一个时间戳给你的动画更新函数,你可以使用这个时间戳来计算动画的进度。function draw(timestamp) { const progress = timestamp / 1000; // 将时间戳转换为秒 const angle = progress * Math.PI; // 计算旋转角度 // 根据旋转角度更新元素的位置 // ... requestAnimationFrame(draw); } requestAnimationFrame(draw);
-
使用
cancelAnimationFrame
来停止动画: 如果你想停止动画,可以使用cancelAnimationFrame
函数。你需要将requestAnimationFrame
的返回值传递给cancelAnimationFrame
。let animationId = requestAnimationFrame(drawCircle); // 停止动画 cancelAnimationFrame(animationId);
-
兼容性处理: 虽然
requestAnimationFrame
已经被广泛支持,但为了兼容一些老旧的浏览器,你可以使用以下代码来进行兼容性处理:window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; window.cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame;
总结
requestAnimationFrame
是 Canvas 动画的必备工具,它可以让你告别卡顿,让你的像素们跳起流畅的华尔兹。
记住,不要再使用 setInterval
和 setTimeout
来做动画了,它们会让你“卡成翔”。拥抱 requestAnimationFrame
,让你的动画飞起来!
希望这篇文章能帮助你更好地理解 Canvas 动画,并让你在动画的道路上越走越远。现在,去尝试用 requestAnimationFrame
创建一些令人惊叹的动画吧! 记住,创造的乐趣才是编程的真谛。 祝你玩得开心!