requestAnimationFrame:每次重绘最多只调用一次回调函数
测试开启/关闭requestAnimationFrame的监听事件调用次数差异:
先说结论:存在约8倍的调用次数差距!
requestAnimationFrame使用与否的次数差距
本次测试代码为drag事件
const source = document.getElementById("draggable");
let enqueued = false
let times = 0
source.addEventListener("drag", (event) => {
});
let timeNow = null;
source.addEventListener("dragstart", (event) => {
if(!enqueued) {
timeNow = new Date();
}
});
const target = document.getElementById("droptarget");
target.addEventListener("dragover", (event) => {
// if (!enqueued) {
// enqueued = true
// requestAnimationFrame(() => {
// console.log(event);
console.log(times++);
// enqueued = false
// })
// }
event.preventDefault();
}, false);
target.addEventListener("drop", (event) => {
console.log('done');
console.log(new Date() - timeNow);
});
不开启requestAnimationFrame,测试drag次数:
这是未开启requestAnimationFrame的dragstart到drop时间间隔内的 dragover次数,它们次数/时间间隔分别为:0.4997、0.4705、0.4550、0.5110、0.4148。
平均为:0.4761
const source = document.getElementById("draggable");
let enqueued = false
let times = 0
source.addEventListener("drag", (event) => {
});
let timeNow = null;
source.addEventListener("dragstart", (event) => {
if(!enqueued) {
timeNow = new Date();
}
});
const target = document.getElementById("droptarget");
target.addEventListener("dragover", (event) => {
if (!enqueued) {
enqueued = true
requestAnimationFrame(() => {
console.log(event);
console.log(times++);
enqueued = false
})
}
event.preventDefault();
}, false);
target.addEventListener("drop", (event) => {
console.log('done');
console.log(new Date() - timeNow);
});
加上requestAnimationFrame之后他们的次数/时间间隔为:
0.0597、0.0592、0.0596、0.0581、0.0595
平均为:0.0592
前后差距:0.4761/0.0592约等于8倍的调用次数差距!
requestAnimationFrame与settimeout、setinterval的区别
无论 setInterval()还是 setTimeout()都是不能保证时间精度的。
作为第二个参数的延时 只能保证何时会把代码添加到浏览器的任务队列,不能保证添加到队列就会立即运行。如果队列前面还有其他任务,那么就要等这些任务执行完再执行。简单来讲,这里毫秒延时并不是说何时这些代码会执 行,而只是说到时候会把回调加到任务队列。如果添加到队列后,主线程还被其他任务占用,比如正在 处理用户操作,那么回调就不会马上执行。
requestAnimationFrame()方法接收一个参数,此参数是一个要在重绘屏幕前调用的函数。
它解决了浏览器不知道 JavaScript 动画何时开始的问题, 以及最佳间隔是多少的问题。
传给 requestAnimationFrame()的函数实际上可以接收一个参数,此参数是一个 DOMHighRes- TimeStamp 的实例(比如 performance.now()返回的值),表示下次重绘的时间。这一点非常重要: requestAnimationFrame()实际上把重绘任务安排在了未来一个已知的时间点上,而且通过这个参数 告诉了开发者。基于这个参数,就可以更好地决定如何调优动画了。
与 setTimeout()类似,requestAnimationFrame()也返回一个请求 ID,可以用于通过另一个 方法 cancelAnimationFrame()来取消重绘任务。
let requestID = window.requestAnimationFrame(() => {
console.log('Repaint!');
})
window.cancelAnimationFrame(requestID);
requestAnimationFrame如何实现节流
支持这个方法的浏览器实际上会暴露出作为钩子的回调队列。所谓钩子(hook),就是浏览器在执行下一次重绘之前的一个点。这个回调队列是一个可修改的函数列表,包含应该在重绘之前调用的函数。每次调用 requestAnimationFrame()都会在队列上推入一个回调函数,队列的长度没有限制。
可以保证每次重绘最多只调用一次回调函数。这是一个非常好的节流工具。在频繁执行影响页面外观的代码时(比如滚动事件监听器),可以利用这个回调队列进行节流。例如上文的代码
但是重绘是非常频繁的操作,所以加上requestAnimationFrame之后还不算真正的节流,需要配合定时器
let enabled = true;
function expensiveOperation() {
console.log('Invoked at', Date.now());
}
window.addEventListener('scroll', () => {
if (enabled) {
enabled = false;
window.requestAnimationFrame(expensiveOperation);
window.setTimeout(() => enabled = true, 50);
}
});