requestAnimationFrame vs setTimeout:谁才是前端动画的最优解?

目录

requestAnimationFrame vs setTimeout:两者的性能差异及应用场景

1. setTimeout 和 requestAnimationFrame 简介

2. 性能差异与资源消耗

3. 实际应用场景中的区别:

4. 性能测试与帧率 (FPS) 比较

5. 总结与最佳实践:

6.代码演示差距:

7. 结语:


 

requestAnimationFrame vs setTimeout:两者的性能差异及应用场景

在前端开发中,尤其是在进行动画、滚动、视觉效果等涉及时间控制的操作时,如何优化性能是非常重要的。requestAnimationFramesetTimeout 都是常见的控制动画更新的方式,但它们在性能、精度以及资源消耗方面存在显著差异。本文将深入探讨这两者的区别,并通过实例来展示它们在实际开发中的应用和性能差异。


1. setTimeoutrequestAnimationFrame 简介

  • setTimeout setTimeout 是 JavaScript 中用于延迟执行某个函数的内置方法。它可以设定某个函数在指定的时间后被调用一次。它的基本语法是:

    setTimeout(callback, delay);

    callback 是回调函数,delay 是延迟的时间,单位为毫秒。

    setTimeout 常用于定时执行任务,但在动画的应用中,它并不是最佳选择,因为它会基于指定的时间间隔来执行任务,而不与浏览器的刷新率同步。

  • requestAnimationFrame requestAnimationFrame 是浏览器提供的 API,用于在浏览器下次重绘前执行动画。它能够保证动画与浏览器的刷新频率同步,从而避免了不必要的资源浪费。它的基本语法是:

    requestAnimationFrame(callback);

    callback 是回调函数,通常用来更新动画的状态。requestAnimationFrame 会根据浏览器的刷新率自动调整调用频率,通常是每秒 60 次,即 60 FPS(帧每秒)。


2. 性能差异与资源消耗

  • 流畅度与同步: requestAnimationFrame 是为动画设计的 API,它会在浏览器的重绘周期内执行,通常与显示器的刷新频率同步。这样可以确保动画的平滑性,并避免出现卡顿现象。与此不同,setTimeout 是基于时间间隔的,它并不会考虑浏览器的刷新频率,因此即使设置了 setTimeout 触发动画更新,它的执行频率也并不稳定,可能导致动画出现不流畅或丢帧的现象。

  • CPU 与内存占用: 一个显著的差异是,requestAnimationFrame 会在页面不可见时(如切换到后台 tab)暂停执行,从而节省了 CPU 和内存资源。它在用户再次切回页面时恢复动画。而 setTimeout 则会继续执行,导致不必要的资源消耗,尤其是当页面在后台时,setTimeout 仍然会频繁执行,浪费 CPU 资源。


3. 实际应用场景中的区别:

通过简单的例子,我们可以明显看到两者的区别。假设我们要实现一个小球上下弹跳的动画:

  • 使用 setTimeout 即使切换到后台 tab,动画仍然继续运行,导致 CPU 使用率高,并且动画可能卡顿。

  • 使用 requestAnimationFrame 切换到后台 tab 后,动画会暂停,CPU 使用率大大减少;当用户切回页面时,动画从暂停的地方继续,并且保持流畅。

通过这种方式,requestAnimationFrame 显著提高了性能,并且在移动端和低性能设备上尤其能够节省大量资源。


4. 性能测试与帧率 (FPS) 比较

为了直观地展示差异,我们可以通过计算每秒帧数(FPS)来比较 setTimeoutrequestAnimationFrame 的表现:

  • FPS 计算: FPS 反映了每秒更新多少帧图像。在理想情况下,动画的 FPS 应该接近 60,这对应于浏览器的刷新频率。

  • setTimeout 即使设置为每 16 毫秒调用一次,setTimeout 的执行并不会精确地与浏览器刷新同步,可能会导致每秒的帧数低于预期,尤其是在切换 tab 后,动画会继续执行,占用大量资源。

  • requestAnimationFrame 在浏览器的渲染循环中,requestAnimationFrame 会自动调节执行频率,保证每秒接近 60 帧。如果切换 tab,FPS 会降至 0,避免了无用的资源消耗。


5. 总结与最佳实践:

  • requestAnimationFrame 的优势:

    • 流畅性:与浏览器刷新率同步,保证动画平滑运行。

    • 性能:切换到后台时自动暂停,节省系统资源。

    • 精度:可以准确地计算动画帧,避免因定时器精度不高导致的卡顿。

    • 浏览器优化:浏览器对 requestAnimationFrame 有专门优化,使其在性能上表现优越。

  • setTimeout 的缺点:

    • 不同步:无法与浏览器的渲染周期同步,可能导致动画不流畅。

    • 资源浪费:在后台执行时,依然占用 CPU 资源,影响设备性能。


6.代码演示差距:

我们简单创建一个空文件写入以下内容:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>RequestAnimationFrame vs setTimeout</title>
  <style>
    body {
      margin: 0;
      overflow: hidden;
      background-color: #333;
      color: white;
      font-family: Arial, sans-serif;
    }
    canvas {
      display: block;
      margin: 0 auto;
    }
    #controls {
      position: absolute;
      top: 10px;
      left: 10px;
      color: white;
      font-size: 16px;
    }
    #fps {
      position: absolute;
      bottom: 10px;
      left: 10px;
      color: white;
      font-size: 16px;
    }
  </style>
</head>
<body>
  <div id="controls">
    <button onclick="toggleAnimation()">切换动画</button>
  </div>
  <canvas id="canvas"></canvas>
  <div id="fps"></div>

  <script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    let width = canvas.width = window.innerWidth;
    let height = canvas.height = window.innerHeight;

    // 动画控制
    let useRequestAnimationFrame = true; // 默认为使用 requestAnimationFrame

    window.addEventListener('resize', () => {
      width = canvas.width = window.innerWidth;
      height = canvas.height = window.innerHeight;
    });

    // 小球对象
    class Ball {
      constructor() {
        this.reset();
      }

      reset() {
        this.x = Math.random() * width;
        this.y = Math.random() * height / 2;
        this.radius = 30;
        this.dy = 2 + Math.random() * 4;
        this.color = `hsl(${Math.random() * 360}, 100%, 70%)`;
      }

      update() {
        this.y += this.dy;
        if (this.y > height - this.radius || this.y < this.radius) {
          this.dy *= -1;
        }
      }

      draw() {
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        ctx.fillStyle = this.color;
        ctx.fill();
      }
    }

    const ball = new Ball();

    // FPS 计算
    let lastTime = 0;
    let frames = 0;
    let fps = 0;

    function calculateFPS() {
      frames++;
      const now = performance.now();
      const deltaTime = now - lastTime;

      if (deltaTime >= 1000) {
        fps = frames;
        frames = 0;
        lastTime = now;
        document.getElementById("fps").innerText = `FPS: ${fps}`;
      }
    }

    // 动画核心:使用 requestAnimationFrame 或 setTimeout 控制
    let animationFrameId;

    function animateWithRequestAnimationFrame() {
      ctx.clearRect(0, 0, width, height);
      ball.update();
      ball.draw();
      calculateFPS();
      animationFrameId = requestAnimationFrame(animateWithRequestAnimationFrame);
    }

    function animateWithSetTimeout() {
      ctx.clearRect(0, 0, width, height);
      ball.update();
      ball.draw();
      calculateFPS();
      setTimeout(animateWithSetTimeout, 16); // 16ms ≈ 60fps
    }

    function startAnimation() {
      if (useRequestAnimationFrame) {
        animateWithRequestAnimationFrame();
      } else {
        animateWithSetTimeout();
      }
    }

    function toggleAnimation() {
      useRequestAnimationFrame = !useRequestAnimationFrame;
      cancelAnimationFrame(animationFrameId);
      ball.reset();
      startAnimation();
    }

    startAnimation();
  </script>
</body>
</html>

 


 

7. 结语:

总的来说,requestAnimationFrame 是做动画的最佳选择,特别是在需要平滑、高效的动画体验时。而 setTimeout 由于其不稳定性和资源消耗问题,并不适合用于动画处理。在开发中,尽量选择 requestAnimationFrame 来优化动画效果,并保证更好的性能,尤其是在移动端和低性能设备上。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值