当前位置: 首页 > article >正文

JavaScript 的 requestAnimationFrame

在现代 Web 开发中,用户体验至关重要。动画作为用户交互的重要组成部分,如果处理不当,很容易出现卡顿、掉帧等问题,严重影响用户体验。幸运的是,JavaScript 提供了一个强大的 API:requestAnimationFrame(简称 rAF),它为我们创建平滑、高效的动画提供了坚实的基础。本文将深入探讨 requestAnimationFrame 的原理、使用、高级技巧以及在实际项目中的应用,帮助你掌握动画开发的精髓。

requestAnimationFrame动画

requestAnimationFrame 是一个浏览器 API,其核心作用是:在浏览器下一次重绘之前,请求浏览器调用指定的函数。这听起来可能很简单,但其背后的机制却非常强大,它让动画的执行与浏览器的渲染流程紧密结合,从而避免了传统方法中的许多问题。

rAF 的运行机制

与 setTimeout 和 setInterval 等定时器 API 不同,rAF 的执行时机是由浏览器控制的。这意味着:

  1. 同步渲染: rAF 的回调函数会在浏览器执行重绘操作之前调用,确保了动画与浏览器的渲染流程同步。这种同步性使得动画帧率与显示器的刷新率保持一致(通常为 60Hz,即每秒 60 帧),从而保证了动画的流畅性。

  2. 智能调度: 浏览器会智能地调度 rAF 的回调函数,避免在浏览器不可见时执行动画,从而节省系统资源和电量。当页面处于非活动状态(如切换到其他标签页)时,rAF 会暂停执行,直到页面重新可见时才恢复。

  3. 高精度时间戳: rAF 的回调函数会接收一个高精度的时间戳参数 timestamp,该参数表示回调函数执行的时间点。我们可以利用这个时间戳来精确计算动画的进度,避免动画出现不连贯的情况。

requestAnimationFrame 的基本使用

我们先来看一个最基本的 rAF 使用示例:

const element = document.getElementById('animated-element');
let start = null; // 动画开始的时间戳
let duration = 1500; // 动画持续时间,单位毫秒
let startOpacity = 0; // 起始透明度
let endOpacity = 1; // 结束透明度

function animate(timestamp) {
  if (!start) start = timestamp; // 如果 start 为空,则将当前时间戳赋值给 start
  const progress = (timestamp - start) / duration; // 计算动画进度,取值 0 - 1 之间

  // 根据进度更新元素的透明度
  element.style.opacity = startOpacity + (endOpacity - startOpacity) * progress;

  if (progress < 1) {
      requestAnimationFrame(animate); // 循环调用 requestAnimationFrame
    } else {
        // 动画结束,设置 opacity 为最终值
        element.style.opacity = endOpacity;
        start = null; // 重置 start 变量
    }
}

requestAnimationFrame(animate); // 启动动画

在这个例子中,我们:

  1. 获取了需要动画的元素(这里假设它有一个 id 为 animated-element 的元素)。

  2. 定义了一个 animate 函数,该函数作为 rAF 的回调函数。

  3. 在 animate 函数中:

    • 计算动画的进度(progress),它的值从 0 增加到 1。

    • 根据进度更新元素的 opacity 属性,实现透明度的渐变效果。

    • 如果动画尚未完成(progress < 1),则再次调用 requestAnimationFrame(animate),请求浏览器执行下一帧的动画。否则将属性设置为最终状态,并且重置 start 变量。

  4. 最后,使用 requestAnimationFrame(animate) 启动动画。

动画的平滑过渡:使用缓动函数

在上面的例子中,动画的进度是线性变化的。这意味着动画的变化速度是恒定的。然而,现实世界中大部分的运动都是非线性的。为了让动画看起来更自然,我们可以使用缓动函数(easing functions)。

缓动函数是一种数学函数,它定义了动画进度与实际值之间的关系。通过使用缓动函数,我们可以实现加速、减速、弹性等各种动画效果。例如,以下是一个简单的 ease-in-out 函数,可以实现动画的先加速后减速效果:

function easeInOutQuad(t) {
  return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}

function animate(timestamp) {
    if (!start) start = timestamp;
    const progress = (timestamp - start) / duration;
    const easedProgress = easeInOutQuad(progress); // 使用缓动函数处理进度
    element.style.opacity = startOpacity + (endOpacity - startOpacity) * easedProgress;

     if (progress < 1) {
      requestAnimationFrame(animate); // 循环调用 requestAnimationFrame
    } else {
        element.style.opacity = endOpacity;
        start = null;
    }
  }

你可以找到许多现成的缓动函数库,例如 Tween.js 或直接在网上搜索。

cancelAnimationFrame:停止动画

当不再需要动画时,我们可以使用 cancelAnimationFrame(requestID) 函数来停止动画。requestID 是 requestAnimationFrame 函数返回的唯一标识符。

let animationId = requestAnimationFrame(animate);

// ... 一段时间后停止动画
setTimeout(() => {
    cancelAnimationFrame(animationId);
    console.log('动画已停止');
  }, 5000);

rAF 的实际应用场景

除了简单的元素过渡动画之外,rAF 在 Web 开发中还有很多其他应用场景:

  • Canvas 动画: 在 Canvas 上绘制动画,如游戏、数据可视化等。
  • WebGL 动画: 创建复杂的 3D 动画。
  • 滚动动画: 实现页面滚动时的视差效果、固定元素、滚动加载等。
  • 手势识别: 在触摸设备上实现流畅的手势动画。
  • 时间驱动的动画: 在需要使用精确时间的动画,例如倒计时、进度条等。
  • UI 更新: 在进行频繁的 UI 更新时,使用 rAF 可以避免页面卡顿。

与 setTimeout 和 setInterval 的对比

特性requestAnimationFramesetTimeout / setInterval
动画帧率与显示器刷新率同步 (通常 60Hz)不稳定,依赖浏览器调度和系统资源
执行时机在浏览器重绘之前在指定时间后,不保证在重绘前执行
资源消耗更高效,页面不可见时暂停执行容易造成 CPU 资源浪费
动画效果更平滑、避免掉帧可能会出现卡顿、跳帧
浏览器优化允许浏览器进行性能优化不提供优化机制
回调参数接收时间戳参数,可以精确计算动画进度无时间戳参数,精度较低

requestAnimationFrame 的进阶使用技巧

  • 使用类封装动画: 将动画逻辑封装到类中,方便管理和重用。
  • 使用动画库: 可以使用现有的动画库(如 GSAP、Anime.js)来简化动画开发。这些库通常已经做了很多优化,并提供更强大的动画效果。
  • 避免复杂的计算: 在 rAF 的回调函数中尽量避免复杂的计算,以免影响动画的性能。
  • 使用 will-change CSS 属性: 对于需要动画的元素,可以使用 will-change 属性来提前告诉浏览器需要进行动画,从而让浏览器进行性能优化。

总结

requestAnimationFrame 是 JavaScript 中用于创建流畅、高效动画的首选 API。它允许浏览器控制动画的执行时机,从而保证动画的平滑性,避免了传统方式带来的性能问题。通过合理使用 requestAnimationFrame 并结合缓动函数、动画库等工具,我们可以创建出更加炫酷、引人入胜的用户体验。

希望这篇文章可以帮助你更好地理解和使用 requestAnimationFrame! 祝你在动画的探索之路上越走越远。如果对文章有任何疑问或者建议,欢迎在评论区交流。


http://www.kler.cn/a/465182.html

相关文章:

  • C++和OpenGL实现3D游戏编程【连载19】——着色器光照初步(平行光和光照贴图)(附源码)
  • WPS表格技巧01-项目管理中的基本功能-计划和每日记录的对应
  • 现代光学基础4
  • java实验4 反射机制
  • C语言渗透和好网站
  • C# 设计模式:装饰器模式与代理模式的区别
  • 如果Adobe 退出中国后怎么办
  • 安全框架:Apache Shiro
  • Springboot数据层开发 — 整合jdbcTemplate、mybatis
  • Word格式修改
  • Nginx知识详解(理论+实战更易懂)
  • PDF预览插件
  • 服务器数据恢复—离线盘数超过热备盘数导致raidz阵列崩溃的数据恢复
  • 【微软,模型规模】模型参数规模泄露:理解大型语言模型的参数量级
  • 基于MongoDB和PostgreSQL的百货公司进销管理系统
  • 李宏毅机器学习笔记-自注意力机制(self-attention)
  • HTML——57. type和name属性
  • QML学习(一) Qt Quick和QML介绍以及适用场景说明
  • linux最常用最新基础命令
  • vscode实用插件(持续更新)
  • QT集成IntelRealSense双目摄像头3,3D显示
  • 【gopher的java学习笔记】什么是po,vo
  • 南京市建邺区南苑街道一行莅临园区考察交流
  • 【Python3教程】Python3基础篇之List(列表)
  • [网络安全] DVWA之 Command Injection 攻击姿势及解题详析合集
  • C语言----分支语句