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

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

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

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]刪除
最新教學 更多>
  • PHP:揭示動態網站背後的秘密
    PHP:揭示動態網站背後的秘密
    PHP(超文本預處理器)是一種伺服器端程式語言,廣泛用於建立動態和互動式網站。它以其簡單語法、動態內容生成能力、伺服器端處理和快速開發能力而著稱,並受到大多數網站託管服務商的支援。 PHP:揭秘動態網站背後的秘方PHP(超文本預處理器)是伺服器端程式語言,以其用於創建動態和互動式網站而聞名。它廣泛應...
    程式設計 發佈於2024-11-06
  • JavaScript 中的變數命名最佳實踐,實現簡潔、可維護的程式碼
    JavaScript 中的變數命名最佳實踐,實現簡潔、可維護的程式碼
    簡介:增強程式碼清晰度和維護 編寫乾淨、易於理解和可維護的程式碼對於任何 JavaScript 開發人員來說都是至關重要的。實現這一目標的一個關鍵方面是透過有效的變數命名。命名良好的變數不僅使您的程式碼更易於閱讀,而且更易於理解和維護。在本指南中,我們將探討如何選擇具有描述性且有意義的變數名稱,以顯...
    程式設計 發佈於2024-11-06
  • 揭示 Spring AOP 的內部運作原理
    揭示 Spring AOP 的內部運作原理
    在这篇文章中,我们将揭开 Spring 中面向方面编程(AOP)的内部机制的神秘面纱。重点将放在理解 AOP 如何实现日志记录等功能,这些功能通常被认为是一种“魔法”。通过浏览核心 Java 实现,我们将看到它是如何与 Java 的反射、代理模式和注释相关的,而不是任何真正神奇的东西。 ...
    程式設計 發佈於2024-11-06
  • JavaScript ESelease 筆記:釋放現代 JavaScript 的力量
    JavaScript ESelease 筆記:釋放現代 JavaScript 的力量
    JavaScript ES6,正式名稱為 ECMAScript 2015,引入了重大增強功能和新功能,改變了開發人員編寫 JavaScript 的方式。以下是定義 ES6 的前 20 個功能,它們使 JavaScript 程式設計變得更有效率和愉快。 JavaScript ES6 ...
    程式設計 發佈於2024-11-06
  • 了解 Javascript 中的 POST 請求
    了解 Javascript 中的 POST 請求
    function newPlayer(newForm) { fetch("http://localhost:3000/Players", { method: "POST", headers: { 'Content-Type': 'application...
    程式設計 發佈於2024-11-06
  • 如何使用 Savitzky-Golay 濾波平滑雜訊曲線?
    如何使用 Savitzky-Golay 濾波平滑雜訊曲線?
    雜訊資料的平滑曲線:探討Savitzky-Golay 濾波在分析資料集的過程中,平滑雜訊曲線的挑戰出現在提高清晰度並揭示潛在模式。對於此任務,特別有效的方法是 Savitzky-Golay 濾波器。 Savitzky-Golay 濾波器在資料可以透過多項式函數進行局部近似的假設下運作。它利用最小二乘...
    程式設計 發佈於2024-11-06
  • 重載可變參數方法
    重載可變參數方法
    重載可變參數方法 我們可以重載一個採用可變長度參數的方法。 此程式示範了兩種重載可變參數方法的方法: 1 各種可變參數類型:可以重載具有不同可變參數類型的方法,例如 vaTest(int...) 和 vaTest(boolean...)。 varargs 參數的類型決定了要呼叫哪個方法。 2 新...
    程式設計 發佈於2024-11-06
  • 如何在經典類別元件中利用 React Hooks?
    如何在經典類別元件中利用 React Hooks?
    將React Hooks 與經典類組件集成雖然React hooks 提供了基於類的組件設計的替代方案,但可以通過將它們合併到現有類中來逐步採用它們成分。這可以使用高階組件 (HOC) 來實現。 考慮以下類別元件:class MyDiv extends React.component { co...
    程式設計 發佈於2024-11-06
  • 如何使用 Vite 和 React 建立更快的單頁應用程式 (SPA)
    如何使用 Vite 和 React 建立更快的單頁應用程式 (SPA)
    在現代 Web 開發領域,單頁應用程式 (SPA) 已成為建立動態、快速載入網站的熱門選擇。 React 是用於建立使用者介面的最廣泛使用的 JavaScript 程式庫之一,使 SPA 開發變得簡單。然而,如果你想進一步提高你的開發速度和應用程式的整體效能,Vite 是一個可以發揮重大作用的工具。...
    程式設計 發佈於2024-11-06
  • JavaScript 中字串連接的逐步指南
    JavaScript 中字串連接的逐步指南
    JavaScript 中的字串連接 是將兩個或多個字串連接起來形成單一字串的過程。本指南探討了實現此目的的不同方法,包括使用運算子、= 運算子、concat() 方法和範本文字。 每種方法都簡單有效,允許開發人員為各種用例(例如用戶訊息或 URL)建立動態字串。 模板文字尤其為字串連接提供了現...
    程式設計 發佈於2024-11-06
  • Web UX:向使用者顯示有意義的錯誤
    Web UX:向使用者顯示有意義的錯誤
    擁有一個用戶驅動且用戶友好的網站有時可能會很棘手,因為它會讓整個開發團隊將更多時間花在不會為功能和核心業務增加價值的事情上。然而,它可以在短期內幫助用戶並在長期內增加價值。對截止日期嚴格要求的專案經理可能會低估長期的附加價值。我不確定蘋果網站團隊是否屬實,但他們缺少一些出色的使用者體驗。 最近,我...
    程式設計 發佈於2024-11-06
  • 小型機械手
    小型機械手
    小型機械手臂新重大發布 代碼已完全重構並編碼為屬性操作的新支援 這是一個操作範例: $classFile = \Small\ClassManipulator\ClassManipulator::fromProject(__DIR__ . '/../..') ->getC...
    程式設計 發佈於2024-11-06
  • 機器學習專案中有效的模型版本管理
    機器學習專案中有效的模型版本管理
    在机器学习 (ML) 项目中,最关键的组件之一是版本管理。与传统软件开发不同,管理机器学习项目不仅涉及源代码,还涉及随着时间的推移而演变的数据和模型。这就需要一个强大的系统来确保所有这些组件的同步和可追溯性,以管理实验、选择最佳模型并最终将其部署到生产中。在这篇博文中,我们将探索有效管理 ML 模型...
    程式設計 發佈於2024-11-06
  • 如何在 PHP 中保留鍵的同時按列值對關聯數組進行分組?
    如何在 PHP 中保留鍵的同時按列值對關聯數組進行分組?
    在保留鍵的同時按列值對關聯數組進行分組考慮一個關聯數組的數組,每個數組代表一個具有“id”等屬性的實體和“名字”。面臨的挑戰是根據特定列“id”對這些數組進行分組,同時保留原始鍵。 為了實現這一點,我們可以使用 PHP 的 foreach 迴圈來迭代陣列。對於每個內部數組,我們提取“id”值並將其用...
    程式設計 發佈於2024-11-06
  • 如何在 Gradle 中排除特定的傳遞依賴?
    如何在 Gradle 中排除特定的傳遞依賴?
    用Gradle 排除傳遞依賴在Gradle 中,使用應用程式外掛程式產生jar 檔案時,可能會遇到傳遞依賴,您可能想要排除。為此,可以使用排除方法。 排除的預設行為最初,嘗試排除 org.slf4j:slf4j- 的所有實例log4j12 使用以下程式碼:configurations { run...
    程式設計 發佈於2024-11-06

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

Copyright© 2022 湘ICP备2022001581号-3