JavaScript动画-3

125 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言

吃饱饭才有力气写代码~

昨天讲到用requestAnimationFrame()对动画进行调优,以及使用cancelAnimationFrame()取消重绘任务。其实requestAnimationFrame()背后还有很多内容。

支持这个方法的浏览器实际上会暴露出作为钩子的回调队列,这个队列是一个可以修改的函数列表,含有应该在重绘之前调用的函数,每次调用都会在队列上推入一个回调函数,队列的长度没有限制。这个回调队列的行为不一定跟动画有关,不过通过这个函数递归地向队列中加入回调函数可以保证每次重绘最多只调用一次回调函数,所以它可以很好地节流。比如在频繁地执行影响页面外观的代码时,可以用这个回调队列进行节流。
比如:滚动事件监听器
原生实现(其中的滚动事件监听器每次触发都会调用expensiveOperation()函数,当我们向下滚动网页时,这个事件很快就会被触发并执行成千上百次):

function expensiveOperation(){
    console.log('Invoked at',Date.now());
}
window.addEventListener('scroll',() =>{
    expensiveOperation();
})

如果想把事件处理程序的调用限制在每次重绘前发生,那么可以把它封装在requestAnimationFrame()调用中:

function expensiveOperation(){
    console.log('Invoked at',Date.now());
}
window.addEventListener('scroll',() =>{
    window.requestAnimationFrame(expensiveOperation);
});

这样会把所有回调的执行集中在重绘钩子,但不会过滤掉每次重绘的多余调用,这个时候可以给设置一个开关:

let enqueued = false;
function expensiveOperation(){
    console.log('Invoked at',Date.now());
    enqueued = false;
}
window.addEventListener('scroll',() =>{
    if(!enqueued){
        enqueued = true;
        window.requestAnimationFrame(expensiveOperation);
    }  
});

但是重绘很频繁,上面这样还不算真的节流,最好的方法是搭配计时器来限制操作执行的频率,限制实际的操作执行间隔,而requestAnimationFrame()控制在浏览器的哪个渲染周期中执行,如:

let enabled = false;
function expensiveOperation(){
    console.log('Invoked at',Date.now());
}
window.addEventListener('scroll',() =>{
    if(enabled){
        enabled = false;
        window.requestAnimationFrame(expensiveOperation);
        window.setTimeout(() => enabled = true,50)
    }  
});