前端性能优化(计时器/动画)的利器:?requestAnimationFrame?

零 JavaScript教程评论67字数 5452阅读18分10秒阅读模式

前端性能优化(计时器/动画)的利器:?requestAnimationFrame?

前言

前段时间做过一个大屏的需求,它要求页面刚进入时,大屏数据刷新,然后每隔5s进行数据刷新,当时使用的方案是利用setInterval来每隔5s进行数据获取。当时也没感觉有什么问题。直到看到了一些文章,才发现单纯使用setInterval,会导致一些问题。在准确性和性能方面都存在一些问题:

  • 准确性:代码上是写的每5秒,但是实际执行的时候是存在延迟的,当时开发的时候也出现过这个问题,当时因为误差很小就没放在心上。
  • 性能:当我们切屏的时候,大屏页面还是会每隔一段时间进行数据请求,实际上是不需要的。

只不过现在那个需求已经由别的部门做了,所以我也不能继续优化。但是还是需要记录学习下。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16106.html

最优的方案是:requestAnimationFrame + setTimeout文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16106.html

setInterval时间间隔准确吗

使用setInterval函数可以很容易地实现每隔5秒获取数据的功能。以下是一个示例代码:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16106.html

javascript

复制代码
// 定义获取数据的函数
function fetchData() {
    // 在这里编写获取数据的代码,这里只是一个示例
    console.log('Fetching data...');
}

// 设置每隔5秒执行一次fetchData函数
setInterval(fetchData, 5000);
解释

在这个示例中,fetchData函数代表获取数据的操作。setInterval(fetchData, 5000)指定了每隔5秒调用一次fetchData函数。你可以将实际的数据获取逻辑放在fetchData函数内部,以满足你的需求。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16106.html

但是上述函数是真的每隔5s进行数据获取吗?文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16106.html

setInterval函数用于在指定的时间间隔内重复执行指定的代码。在理论上,时间间隔是精确的,但在实践中可能存在一些偏差。这是因为JavaScript是单线程执行的,而且在执行重复代码时可能会受到其他任务的影响,比如浏览器的垃圾回收或者其他JavaScript代码的执行。因此,虽然setInterval的时间间隔通常是准确的,但并不能保证100%的精确性。对于需要精确时间间隔的任务,可能需要考虑使用更可靠的方法,比如使用requestAnimationFrame或者使用setTimeout来动态调整时间间隔。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16106.html

然后我们再参考下MDN中的说明:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16106.html

确保执行时间短于定时器时间间隔文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16106.html

如果你的代码逻辑执行时间可能比定时器时间间隔要长,建议你使用递归调用了 setTimeout() 的具名函数。例如,使用 setInterval() 以 5 秒的间隔轮询服务器,可能因网络延迟、服务器无响应以及许多其他的问题而导致请求无法在分配的时间内完成。因此,你可能会发现排队的 XHR 请求没有按顺序返回。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16106.html

在这些场景下,应首选递归调用 setTimeout() 的模式文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16106.html

js

复制代码
(function loop() {
  setTimeout(function () {
    // Your logic here

    loop();
  }, delay);
})();
解释

setTimeout时间间隔准确吗

上一章节讲到了,使用setInterval的时间间隔不一定准确,那使用setTimeout的时间间隔就准确吗。

我们继续看MDN官网y有关于setTimeout的说明:

有许多因素可能导致 setTimeout 的回调函数执行时间超过预期值,例如嵌套超时、非活动标签超时、追踪型脚本的节流、超时延迟等等。

总的来说,setTimeout 和 setInterval 类似,随着时间的推移,会出现一定的误差。不过,setTimeout 存在一个不足之处,即当程序在后台运行时,setTimeout 仍会持续执行,造成性能浪费。那么,有什么方法可以解决这个问题呢?

详情可见:延时比指定值更长的原因

最终方案:requestAnimationFrame + setTimeout

封装成hooks

js

复制代码
import { useEffect, useRef, useCallback } from 'react'

const usePolling = (callback: any, delay: any) => {
  const savedCallback = useRef<any>()
  const animationFrameId = useRef<any>()

  // 保存新回调
  useEffect(() => {
    savedCallback.current = callback
  }, [callback])

  const tick = useCallback(() => {
    if (savedCallback.current) {
      savedCallback.current()
    }

    animationFrameId.current = requestAnimationFrame(() => {
      setTimeout(tick, delay)
    })
  }, [delay])

  const start = useCallback(() => {
    tick()
  }, [tick])

  const stop = useCallback(() => {
    if (animationFrameId.current) {
      cancelAnimationFrame(animationFrameId.current)
    }
  }, [])

  useEffect(() => {
    start()
    return () => stop()
  }, [start, stop])

  return { start, stop }
}
解释

usePolling用于实现定时轮询功能。它接受两个参数:一个回调函数 callback 和一个延迟时间 delay。在组件中调用该 Hook 后,会创建一个定时器,每隔指定的延迟时间执行一次回调函数。

在这段代码中,使用了 useRef 来保存回调函数和动画帧的 ID,以及 useCallback 来创建稳定的回调函数。在 tick 函数中执行回调函数,并使用 requestAnimationFrame 和 setTimeout 来实现定时轮询。同时,通过 start 和 stop 函数来启动和停止轮询功能。

最后,在 useEffect 钩子中调用 start 函数启动定时轮询,并在组件卸载时调用 stop 函数停止定时器。最后,返回一个包含 start 和 stop 函数的对象。

使用

js

复制代码
import { observer } from 'mobx-react'
import { PAGINATION } from '@edison/common/core/constants/commonConstant'
import { useStores } from '@/stores'


const PollingComponent = () => {
  const { demandManagementStore } = useStores()
  const fetchData = async () => {
    // 替换成你的 API 调用
    await demandManagementStore.getDemandManagementList({ ...PAGINATION })
  }
  fetchData()
  usePolling(fetchData, 5000)

  return (
    <div>
      <h1>Polling Component</h1>
      <p>每5秒轮询一次后台接口</p>
    </div>
  )
}

export default observer(PollingComponent)
解释

requestAnimationFrame介绍

requestAnimationFrame 是一个用于执行动画和其他视觉效果的 JavaScript 方法。它允许你在浏览器下一次重绘之前执行指定的函数,通常用于创建流畅的动画效果,并且能够更好地与浏览器的渲染流程同步,提高性能。

用法

js

复制代码
const animationLoop = () => {
  // 在这里执行动画逻辑
  requestAnimationFrame(animationLoop);
}

// 启动动画循环
requestAnimationFrame(animationLoop);
解释

在上面的示例中,animationLoop 函数用于执行动画逻辑。通过在 animationLoop 函数内部再次调用 requestAnimationFrame,我们可以创建一个持续的循环,使动画持续进行。

requestAnimationFrame 和 setTimeout 在实现定时器功能上基本一致,但 setTimeout 可以自由设置间隔时间,而 requestAnimationFrame 的间隔时间由浏览器决定,大约为 17 毫秒左右。

优势

  1. 更流畅的动画效果: 由于 requestAnimationFrame 会在浏览器下一次重绘前执行,因此可以实现更加流畅的动画效果,避免了传统定时器可能出现的卡顿现象。
  2. 与浏览器渲染同步: requestAnimationFrame 可以让动画与浏览器的渲染流程同步,确保动画的更新在每一帧之前发生,从而提高了性能和视觉体验。
  3. 智能节流: 浏览器会自动优化 requestAnimationFrame 的执行频率,如果页面处于后台标签或隐藏状态,动画将会暂停,从而节省系统资源。

注意事项

  1. 兼容性: 虽然大多数现代浏览器都支持 requestAnimationFrame,但仍需注意其在一些旧版浏览器中的兼容性问题,可能需要提供备用方案。
  2. 性能考量: 虽然 requestAnimationFrame 可以提高性能,但在某些情况下,过度使用可能会导致性能问题,例如在循环中执行过多的计算或DOM操作。
  3. 适度使用: 尽管 requestAnimationFrame 很强大,但并不是所有动画都需要它。对于简单的动画或低优先级的任务,使用传统的定时器可能更合适。

总的来说,requestAnimationFrame 是一个强大的工具,能够帮助你创建流畅、高性能的动画效果,并且在现代的 Web 开发中被广泛应用。

requestAnimationFrame好处

节省CPU性能

  • 使用 setInterval 开启的动画,在页面被隐藏或最小化时,仍然会在后台以极慢的速度运行或停止运行。

js

复制代码
// 按照上面的步骤手动验证下、你发现在后台时仍然是在运行的
let i = 0;
setInterval(() => {
    i++
    console.log("i", i)
},1000)
解释
  • 当页面未激活时,requestAnimationFrame(RAF)会暂停该屏幕的刷新任务。当页面重新激活时,RAF会从上次停留的地方继续执行,从而节省CPU开销。

js

复制代码
let i = 0;
requestAnimationFrame(function fn() {
    i++
    console.log("i", i)
    requestAnimationFrame(fn)
})
解释

不会失帧

  • 就是卡顿问题,具体可参考下一章节。

修复定时器卡顿问题

在页面使用时,使用定时器会卡,而requestAnimationFrame不会卡。

定时器和requestAnimationFrame在实现上有一些不同,导致它们在性能表现上有所区别。

定时器(setTimeout/setInterval)

  1. 定时器是基于事件循环的机制,当定时器到达指定时间时,会将回调函数放入任务队列中,等待事件循环执行。
  2. 定时器的精度可能会受到系统性能和当前页面活动状态的影响,导致定时器不够准确。
  3. 定时器的回调函数执行时机不受垂直同步(垂直空白期)的影响,可能会出现卡顿或不稳定的情况。

requestAnimationFrame

  1. requestAnimationFrame是浏览器提供的专门用于优化动画效果的API,它会在浏览器每一帧渲染前执行回调函数。
  2. requestAnimationFrame会根据浏览器的刷新率(通常是60Hz)来调度回调函数的执行,保证动画的流畅性。
  3. requestAnimationFrame会在每一帧渲染之前执行回调函数,利用浏览器的垂直同步机制,确保动画的稳定性和流畅性。

因此,定时器可能会出现卡顿的情况,而requestAnimationFrame在动画效果中表现更为流畅。如果需要实现动画效果,建议使用requestAnimationFrame来提高性能和用户体验。

定时器可能导致性能问题: 定时器的执行频率受到设定的时间间隔限制,如果时间间隔太短,可能会导致频繁的回调函数执行,从而引发性能问题。而requestAnimationFrame会根据浏览器的帧率自动调整执行频率,通常能够保持较好的性能表现。

requestAnimationFrame应用场景举例

requestAnimationFrame通常用于实现流畅的动画效果,因为它能够充分利用浏览器的刷新率,确保动画的渲染流畅性。以下是一些requestAnimationFrame的应用场景举例:

  1. 游戏开发:用于实现游戏中的动画效果,如人物移动、怪物攻击、子弹飞行等。
  2. 视频播放器:用于实现视频播放器中的播放进度条、缓冲动画等。
  3. 滚动效果:用于实现页面滚动时的平滑滚动效果,如滚动到指定位置时的动画过渡。
  4. 轮播图:用于实现轮播图中的图片切换效果,如淡入淡出、滑动切换等。
  5. 数据可视化:用于实现数据可视化图表中的动画效果,如柱状图增长动画、饼图旋转动画等。

总之,任何需要实现流畅动画效果的场景都可以考虑使用requestAnimationFrame来提高动画的性能和用户体验。

零
  • 转载请务必保留本文链接:https://www.0s52.com/bcjc/javascriptjc/16106.html
    本社区资源仅供用于学习和交流,请勿用于商业用途
    未经允许不得进行转载/复制/分享

发表评论