window.requestAnimationFrame定时器与浏览器重绘

Posted by Mars . Modified at

window.requestAnimationFrame API的功能:精确控制函数在重绘前时间节点执行。

1. 有关浏览器重绘机制的几个事实

  1. 大多数浏览器会限制重绘频率,一般不大于屏幕刷新率60Hz
  2. 浏览器发生重绘的时机不确定,但是两次重绘时间间隔一定不小于(1/60)s,也就是大约16ms
  3. requestAnimationFrame API传入一个函数,控制这个函数精确在紧邻浏览器下次重绘前被调用。

2. requestAnimationFrame的功能和机制

  • requestAnimationFrame 是浏览器提供的一个按帧对网页进行重绘的 API,是为了创建动画而设计的;
  • 传统的setTimeoutsetInterval定时器,确定的只是回调函数加入到任务队列中的时间,而无法确定函数实际执行的时间(前面可能还有别的任务未执行完);
  • requestAnimationFrame采用系统时间来确定间隔,可以保证传入的函数fn,精确地在下一次屏幕刷新前时间点执行;
  • requestAnimationFrame在页面非可见状态下,不会执行传入的函数,而是将他们保存在一个执行队列中,然后在页面恢复可见后立即依次执行;
  • requestAnimationFrame(fn1)中,如果fn1内部再次调用requestAnimationFrame(fn2),则传入的函数fn2会被放入下下次重绘前执行。

3. 取消requestAnimationFrame

和setTimeout一样,通过返回的id进行取消。

let a = requestAnimationFrame()
// 手动取消
cancelAnimationFrame(a)

取消后,requestAnimationFrame(fn)传入函数fn被从重绘前执行的回调队列中清除。

4. 典型应用:过渡的计数器

效果:

过渡的计数器

// Marswiz @2021

let cur = 0;
let counting = false;

function count(target = 600, step = 10, anchor = '#counter') {
    counting = true;
    let container = document.querySelector(anchor);
    let i = cur;

    const show = function() {
        // 调用 requestAnimationFrame
        requestAnimationFrame(() => {
            container.innerText = i;
            i += step;
            cur += step;
            // 在 requestAnimationFrame 内部实现递归调用
            if (i <= target) show();
            else {
                // 递归出口
                container.innerText = target;
                cur = target;
                counting = false;
            }
        });
    }
    
    show();
}

document.querySelector('#btn-100').addEventListener('click', () => {
    if (!counting) count(cur + 100,  Math.floor(100/33));
});
document.querySelector('#btn-500').addEventListener('click', () => {
    if (!counting) count(cur + 500, Math.floor(500/33));
});

参考资料

requestAnimationFrame 执行机制探索

Keywords: JavaScript
previousPost nextPost
已经有 1000000 个小伙伴看完了这篇推文。