requestAnimationFrame进行循环渲染和事件触发限频

requestAnimationFrame进行循环渲染和事件触发限频

定义

requestAnimationFrame的作用是告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。回调函数一般一秒内执行60次,与屏幕刷新频率相同。

requestAnimationFrame的优势

一般来说,如果想要在网页上展示流畅的动画效果,动画的渲染帧率和用户的屏幕刷新率应当保持一致(一般为每秒60帧)。如果动画的帧率低于屏幕刷新率,那么会出现丢帧的问题,具体表现为页面卡顿。下图中位于两次渲染之间的空白帧就是一个page jank(页面垃圾),动画在此处会出现卡顿。
在这里插入图片描述
即使做到渲染操作与屏幕刷新保持一致,这些计算仍在主线程上运行,这意味着当应用程序运行JavaScript时,渲染流程可能会被阻塞,具体如下图所示,在JS执行的过程中,动画渲染被阻塞,导致出现大量的页面垃圾。
在这里插入图片描述
更好的方式是将JS运算切成很多小分片,并使用requestAnimationFrame()来安排它们在帧间空白时间内运行(图中的黄色块),避免其长时间阻塞渲染进程。
在这里插入图片描述

与setInterval、setTimeout的比较

requestAnimationFrame可以减少setInterval、setTimeout带来的跳帧、闪烁和画面割裂,提高动画的质量。

  • 画面割裂:在显示扫描过程中途向显示缓冲区显示新的画布缓冲区时,由于动画位置不匹配而导致画面出现剪切线。
  • 闪烁:在完全渲染画布之前将画布缓冲区呈现给显示缓冲区时,会导致闪烁
  • 跳帧:当渲染帧之间的时间与显示硬件不精确同步时,会导致跳帧。大多数设备的帧率是每秒60帧(或多倍),从而每16.666 … ms产生一个新帧,而计时器setTimeoutsetInterval使用整数值,它们永远无法与设备帧率完美匹配

动画循环渲染

let lastRenderTimestamp = null
let drawTimer = null
let renderFPS = 1 / 60

function render(timestamp) {
    if (lastRenderTimestamp && timestamp - lastRenderTimestamp < renderFPS) {
        return;
    }
    lastRenderTimestamp = timestamp;
    // render something
    drawTimer = window.requestAnimationFrame(render)
}
drawTimer = window.requestAnimationFrame(render)

// 如果想结束渲染
window.cancelAnimationFrame(drawTimer)

事件触发限频

let needRun = true

function update() {
    needRun = true
    // run event handler
}

window.addEventListener('mousemove', (e) => {
    if (needRun) {
        needRun = false
        window.requestAnimationFrame(update)
    }
}, false)