Canvas 动画:利用 `requestAnimationFrame` 实现流畅渲染

Canvas 动画:让你的像素舞动起来,告别卡顿时代

想象一下,你正在用 Canvas 画一个太阳,你想让它缓缓升起,光芒四射。如果你直接用 setInterval 或者 setTimeout 来控制它的位置,你会发现太阳升起的过程断断续续,就像得了帕金森一样,完全没有那种丝滑的日出感。

这感觉是不是很糟糕?就像你精心准备了一场浪漫的求婚,结果背景音乐卡碟,气氛全无。

别担心,今天我们就来聊聊 Canvas 动画的秘密武器——requestAnimationFrame,它可以让你告别卡顿,让你的像素们跳起流畅的华尔兹。

为什么传统的定时器会让你“卡成翔”?

在深入 requestAnimationFrame 之前,我们先来了解一下为什么 setIntervalsetTimeout 在动画方面表现得如此糟糕。

简单来说,它们的问题在于“不够懂浏览器的心”。

  • 刷新频率不一致: 浏览器会定期刷新屏幕,这个刷新频率通常是 60Hz,也就是每秒刷新 60 次。而 setIntervalsetTimeout 只能按照你设定的时间间隔执行,它们无法精确地与浏览器的刷新频率同步。这意味着,你的动画更新可能发生在浏览器刷新之前、之后或者正好在刷新的时候,这会导致画面撕裂、抖动等问题。

    想象一下,你是一名舞蹈指挥,你让舞者每 10 毫秒跳一下,但是舞台的灯光每 16.6 毫秒闪一下。舞者的动作和灯光不同步,结果就是一场混乱的表演。

  • CPU 负担重: 即使你设置的定时器间隔很短,浏览器也不一定会按照你的设定来执行。当浏览器忙于其他任务时,它可能会延迟定时器的执行。更糟糕的是,即使浏览器空闲,它也可能会浪费大量的 CPU 资源来执行不必要的动画更新,因为有些更新可能发生在屏幕刷新之间,用户根本看不到。

    这就像你雇了一个园丁,让他每分钟修剪一次草坪,即使草坪根本没长高。这不仅浪费了钱,还可能把草坪修剪得坑坑洼洼。

  • 标签页不可见时的浪费: 当你的 Canvas 动画在一个不可见的标签页中运行时,setIntervalsetTimeout 仍然会继续执行,这会浪费大量的 CPU 资源和电量。

    这就好比你家里装了一个自动浇花系统,即使你不在家,它仍然会按照设定的时间浇花,这不仅浪费了水,还可能把你的花淹死。

requestAnimationFrame:浏览器的好伙伴,动画的真爱

requestAnimationFrame 就像是浏览器专门为动画定制的 API。它告诉浏览器:“嘿,我有一个动画需要更新,请在下一次屏幕刷新之前执行我的更新函数。”

requestAnimationFrame 的优势在于:

  • 与浏览器刷新同步: requestAnimationFrame 会自动与浏览器的刷新频率同步,确保你的动画更新发生在屏幕刷新之前,从而避免画面撕裂和抖动。

    这就像你让舞蹈指挥和灯光师一起工作,确保舞者的动作和灯光完美同步,呈现出一场精彩的表演。

  • 节省 CPU 资源: requestAnimationFrame 只会在屏幕需要刷新时才执行更新函数,从而节省了大量的 CPU 资源。

    这就像你雇了一个园丁,让他只在草坪长高时才修剪,这样既能保持草坪的美观,又能节省开支。

  • 标签页不可见时暂停: 当你的 Canvas 动画在一个不可见的标签页中运行时,requestAnimationFrame 会自动暂停执行,从而节省 CPU 资源和电量。

    这就像你家里装了一个智能浇花系统,它只有在你需要的时候才会浇花,这样既能保证花卉的生长,又能节省水资源。

如何使用 requestAnimationFrame

使用 requestAnimationFrame 非常简单,只需要三步:

  1. 定义你的动画更新函数: 这个函数负责更新 Canvas 上的元素的位置、大小、颜色等属性。

  2. 使用 requestAnimationFrame 请求下一次动画更新: 在你的动画更新函数中,使用 requestAnimationFrame 再次请求下一次动画更新。

  3. 启动动画: 调用 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 动画的必备工具,它可以让你告别卡顿,让你的像素们跳起流畅的华尔兹。

记住,不要再使用 setIntervalsetTimeout 来做动画了,它们会让你“卡成翔”。拥抱 requestAnimationFrame,让你的动画飞起来!

希望这篇文章能帮助你更好地理解 Canvas 动画,并让你在动画的道路上越走越远。现在,去尝试用 requestAnimationFrame 创建一些令人惊叹的动画吧! 记住,创造的乐趣才是编程的真谛。 祝你玩得开心!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注