」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 增強您的 Web 動畫:像專業人士一樣最佳化 requestAnimationFrame

增強您的 Web 動畫:像專業人士一樣最佳化 requestAnimationFrame

發佈於2024-11-06
瀏覽:726

Supercharge Your Web Animations: Optimize requestAnimationFrame Like a Pro

流畅且高性能的动画在现代 Web 应用程序中至关重要。然而,管理不当可能会使浏览器的主线程过载,导致性能不佳和动画卡顿。 requestAnimationFrame (rAF) 是一种浏览器 API,旨在将动画与显示器的刷新率同步,从而确保与 setTimeout 等替代方案相比更流畅的运动。但有效使用 rAF 需要仔细规划,尤其是在处理多个动画时。

在这篇文章中,我们将探讨如何通过集中动画管理、引入FPS控制、保持浏览器主线程响应来优化requestAnimationFrame。


了解 FPS 及其重要性

在讨论动画性能时,每秒帧数 (FPS) 至关重要。大多数屏幕以 60 FPS 刷新,这意味着 requestAnimationFrame 每秒被调用 60 次。为了保持流畅的动画,浏览器必须在每帧约 16.67 毫秒内完成其工作。

如果在单个帧期间运行太多任务,浏览器可能会错过其目标帧时间,从而导致卡顿或丢帧。降低某些动画的 FPS 有助于减少主线程的负载,从而在性能和流畅度之间取得平衡。

具有 FPS 控制功能的集中式动画管理器可实现更好的性能

为了更有效地管理动画,我们可以通过共享循环集中处理动画,而不是在代码中分散多个 requestAnimationFrame 调用。集中式方法可最大程度地减少冗余调用,并更轻松地添加 FPS 控制。

下面的AnimationManager类允许我们在控制目标FPS的同时注册和取消注册动画任务。默认情况下,我们的目标是 60 FPS,但这可以根据性能需求进行调整。

class AnimationManager {
  private tasks: Set = new Set();
  private fps: number = 60; // Target FPS
  private lastFrameTime: number = performance.now();
  private animationId: number | null = null; // Store the animation frame ID

  private run = (currentTime: number) => {
    const deltaTime = currentTime - this.lastFrameTime;

    // Ensure the tasks only run if enough time has passed to meet the target FPS
    if (deltaTime > 1000 / this.fps) {
      this.tasks.forEach((task) => task(currentTime));
      this.lastFrameTime = currentTime;
    }

    this.animationId = requestAnimationFrame(this.run);
  };

  public registerTask(task: FrameRequestCallback) {
    this.tasks.add(task);
    if (this.tasks.size === 1) {
      this.animationId = requestAnimationFrame(this.run); // Start the loop if this is the first task
    }
  }

  public unregisterTask(task: FrameRequestCallback) {
    this.tasks.delete(task);
    if (this.tasks.size === 0 && this.animationId !== null) {
      cancelAnimationFrame(this.animationId); // Stop the loop if no tasks remain
      this.animationId = null; // Reset the ID
    }
  }
}

export const animationManager = new AnimationManager();

在此设置中,我们计算帧之间的 deltaTime,以确定基于目标 FPS 的下一次更新是否已经过去了足够的时间。这使我们能够限制更新频率,以确保浏览器的主线程不会过载。


实际示例:为具有不同属性的多个元素设置动画

让我们创建一个示例,其中我们为三个盒子设置动画,每个盒子都有不同的动画:一个缩放,另一个改变颜色,第三个旋转。

HTML 如下:

CSS 如下:

.animated-box {
  width: 100px;
  height: 100px;
  background-color: #3498db;
  transition: transform 0.1s ease;
}

现在,我们将添加 JavaScript 来为每个具有不同属性的框设置动画。一个将缩放,另一个将改变其颜色,第三个将旋转。

第 1 步:添加线性插值 (lerp)

线性插值 (lerp) 是动画中用于在两个值之间平滑过渡的常用技术。它有助于创建渐进且平滑的进程,使其成为随时间推移缩放、移动或更改属性的理想选择。该函数采用三个参数:起始值、结束值和标准化时间 (t),该时间确定过渡的距离。

function lerp(start: number, end: number, t: number): number {
  return start   (end - start) * t;
}

第 2 步:缩放动画

我们首先创建一个函数来为第一个框的缩放设置动画:

function animateScale(
  scaleBox: HTMLDivElement,
  startScale: number,
  endScale: number,
  speed: number
) {
  let scaleT = 0;

  function scale() {
    scaleT  = speed;
    if (scaleT > 1) scaleT = 1;

    const currentScale = lerp(startScale, endScale, scaleT);
    scaleBox.style.transform = `scale(${currentScale})`;

    if (scaleT === 1) {
      animationManager.unregisterTask(scale);
    }
  }

  animationManager.registerTask(scale);
}

第3步:彩色动画

接下来,我们为第二个框的颜色变化设置动画:

function animateColor(
  colorBox: HTMLDivElement,
  startColor: number,
  endColor: number,
  speed: number
) {
  let colorT = 0;

  function color() {
    colorT  = speed;
    if (colorT > 1) colorT = 1;

    const currentColor = Math.floor(lerp(startColor, endColor, colorT));
    colorBox.style.backgroundColor = `rgb(${currentColor}, 100, 100)`;

    if (colorT === 1) {
      animationManager.unregisterTask(color);
    }
  }

  animationManager.registerTask(color);
}

第四步:旋转动画

最后,我们创建旋转第三个盒子的函数:

function animateRotation(
  rotateBox: HTMLDivElement,
  startRotation: number,
  endRotation: number,
  speed: number
) {
  let rotationT = 0;

  function rotate() {
    rotationT  = speed; // Increment progress
    if (rotationT > 1) rotationT = 1;

    const currentRotation = lerp(startRotation, endRotation, rotationT);
    rotateBox.style.transform = `rotate(${currentRotation}deg)`;

    // Unregister task once the animation completes
    if (rotationT === 1) {
      animationManager.unregisterTask(rotate);
    }
  }

  animationManager.registerTask(rotate);
}

第 5 步:启动动画

最后,我们可以启动所有三个框的动画:

// Selecting the elements
const scaleBox = document.querySelector("#animate-box-1") as HTMLDivElement;
const colorBox = document.querySelector("#animate-box-2") as HTMLDivElement;
const rotateBox = document.querySelector("#animate-box-3") as HTMLDivElement;

// Starting the animations
animateScale(scaleBox, 1, 1.5, 0.02); // Scaling animation
animateColor(colorBox, 0, 255, 0.01); // Color change animation
animateRotation(rotateBox, 360, 1, 0.005); // Rotation animation

主线程注意事项

使用 requestAnimationFrame 时,必须记住动画在浏览器的主线程上运行。主线程超载过多的任务可能会导致浏览器错过动画帧,从而导致卡顿。这就是为什么使用集中式动画管理器和 FPS 控制等工具优化动画可以帮助保持流畅度,即使有多个动画也是如此。


结论

在 JavaScript 中有效管理动画需要的不仅仅是使用 requestAnimationFrame。通过集中动画并控制 FPS,您可以确保动画更流畅、性能更高,同时保持主线程响应能力。在此示例中,我们展示了如何使用单个 AnimationManager 处理多个动画,演示如何优化性能和可用性。虽然为了简单起见,我们专注于保持一致的 FPS,但这种方法可以扩展到处理各种动画的不同 FPS 值,尽管这超出了本文的范围。

Github 存储库: https://github.com/JBassx/rAF-optimization
StackBlitz: https://stackblitz.com/~/github.com/JBassx/rAF-optimization

LinkedIn: https://www.linkedin.com/in/josephciullo/

版本聲明 本文轉載於:https://dev.to/josephciullo/supercharge-your-web-animations-optimize-requestanimationframe-like-a-pro-22i5?1如有侵犯,請聯絡[email protected]刪除
最新教學 更多>

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3