掌握 JavaScript 中高效的窗口滚动事件处理:最佳实践和技巧
如果不正确处理这些滚动事件,可能会导致严重的性能问题:
本文介绍的做法尤其适用于互动性强的网站。它们将帮助您在中心空间更高效地实现滚动事件。让我们深入了解一下。
**TL:DR**
您可以在此处找到本文的代码沙箱
使用专用的滚动实体
虽然不能直接提高应用的性能,但将所有滚动事件收集到单个函数或类中是个好主意。它使事件的管理和调试更加容易,代码也更加易读。
function scrollHandler(fns) {
window.addEventListener('scroll', () => {
fns.forEach(fn => fn())
})
}
scrollHandler([
() => {
console.log('hello')
},
() => {
console.log('world')
}
])浏览器仍将在每次滚动事件时运行所有功能,因此我们还需要进行一些优化。
使用队列
当触发滚动事件的回调函数时,浏览器始终会等待所有函数执行完毕。因此,将所有事件处理程序收集到单个实体中可能会导致巨大的性能下降。幸运的是,我们可以通过使用队列来缓解这种情况。
请注意,我们使用数组作为队列结构,这可能不是最有效的选择。查看本文,了解如何在 Javascript 中实现自己的队列结构
function scrollHandler(fns) {
window.addEventListener('scroll', () => {
const queue = [...fns]
function next() {
const fn = queue.shift()
if (fn) {
fn()
requestAnimationFrame(next)
}
}
next()
})
}通过使用“requestAnimationFrame”,我们让浏览器有时间在调用队列中的下一个函数之前处理其他任务。但是,队列仍然无法解决每次用户滚动时都会触发每个事件的问题。有两种方法可以解决这个问题。
延迟(节流)滚动事件
最好不要在每次滚动时都调用每个函数,而是提供每秒固定的速率。这就是节流的目的。
我们可以在前两种方法的基础上实现一个节流功能,每次监听滚动事件但每 200 毫秒仅执行一次。
function scrollHandlerThrottle(fns) {
let scrolling = false;
window.addEventListener('scroll', () => {
if (!scrolling) {
scrolling = true;
const queue = [...fns];
function next() {
const fn = queue.shift();
if (fn) {
fn();
requestAnimationFrame(next);
}
}
next();
setTimeout(() => {
scrolling = false;
}, 200);
}
})
}因此,函数调用的最大数量被限制为每秒 5 次。您可以通过更改“setTimeout”的第二个参数来调整限制。
节流与高效队列结构相结合是缓解浏览器阻塞过程的好方法。但是,如果您想进一步限制函数执行,还有另一种方法。
等待(防抖动)滚动事件
节流的另一种方法是防抖动。在这种情况下,这意味着等待用户完成滚动。虽然与节流相比交互性较差,但它是一种很好的替代方案,并且根据用例,也更高效。
我们要做的就是实现一个在初始滚动事件注册后 200 毫秒调用的超时。
function scrollHandlerDevounce(fns) {
let scrollTimeout = null;
window.addEventListener('scroll', () => {
if (scrollTimeout) {
clearInterval(scrollTimeout);
}
scrollTimeout = setTimeout(() => {
const queue = [...fns];
function next() {
const fn = queue.shift();
if (fn) {
fn();
requestAnimationFrame(next);
}
}
next();
}, 200);
});
}每当用户滚动时,只有当滚动停止相当长一段时间时才会触发事件。
您甚至可以更进一步,将这些方法与其他浏览器功能(例如,观察者 API)相结合,以吸引用户的兴趣或执行异步操作。