正如我们所知,它经常被建议去抖动滚动侦听器,以便在用户滚动时UX更好。
但是,我经常发现libraries和articles像Paul Lewis这样有影响力的人推荐requestAnimationFrame
。但是,随着Web平台的快速发展,某些建议可能会随着时间的推移而被弃用。
我看到的问题是处理滚动事件有很多不同的用例,例如构建视差网站,或处理无限滚动和分页。
我看到3个可以在UX方面有所作为的主要工具:
所以,我想知道,每个用例(我只有2个,但你可以拿出其他的),我现在应该使用什么样的工具来获得非常好的滚动体验?
更准确地说,我的主要问题与无限滚动视图和分页(通常不必触发视觉动画,但我们想要一个良好的滚动体验)更相关,是否更好地替换{{1}使用requestAnimationFrame
+被动滚动事件处理程序的组合?我还想知道何时使用requestIdleCallback
来调用API或处理API响应以使滚动执行得更好,或者浏览器可能已经为我们处理了什么?
答案 0 :(得分:84)
虽然这个问题有点老了,但是我想回答它,因为我经常看到脚本,很多这些技术都被误用了。
一般来说,所有问题工具(rAF
,rIC
和被动听众)都是很棒的工具,很快就会消失。但你必须知道为什么要使用它们。
在我开始之前:如果您生成滚动同步/滚动链接效果(如视差效果/粘性元素),使用rIC
限制,setTimeout
没有意义,因为您想立即做出反应
requestAnimationFrame
rAF
在浏览器想要计算文档的新样式和布局之前,为您提供帧生命周期内的点。这就是为什么它非常适合用于动画。首先,它不会比浏览器计算布局(右频率)更经常或更少地被调用。其次,在浏览器计算布局之前调用它(正确的时间)。事实上,使用rAF
进行任何布局更改(DOM或CSSOM更改)都很有意义。 rAF
与V-SYNC同步,与浏览器中任何其他布局呈现相关的东西一样。
rAF
进行油门/去抖动Paul Lewis的默认示例如下:
var scheduledAnimationFrame;
function readAndUpdatePage(){
console.log('read and update');
scheduledAnimationFrame = false;
}
function onScroll (evt) {
// Store the scroll value for laterz.
lastScrollY = window.scrollY;
// Prevent multiple rAF callbacks.
if (scheduledAnimationFrame){
return;
}
scheduledAnimationFrame = true;
requestAnimationFrame(readAndUpdatePage);
}
window.addEventListener('scroll', onScroll);
这种模式经常被使用/复制,虽然它在实践中几乎没有任何意义。 (而且我问自己为什么没有开发人员看到这个明显的问题。)一般来说,理论上将所有内容限制在至少rAF
是很有意义的,因为它没有意义从浏览器请求布局更改的频率高于浏览器呈现布局。
但是,每次浏览器呈现滚动位置更改时,都会触发scroll
事件。这意味着scroll
事件与页面呈现同步。与rAF
给你的字面意思相同。这意味着用某种东西限制某些东西没有任何意义,这已经被每个定义完全相同的东西限制。
在实践中,您可以通过添加console.log
来查看我刚刚说过的内容,并检查此模式"防止多个rAF回调的频率" (答案是否定的,否则会出现浏览器错误。)
// Prevent multiple rAF callbacks.
if (scheduledAnimationFrame){
console.log('prevented rAF callback');
return;
}
正如您将看到这段代码永远不会被执行,它只是死代码。
但是有一个非常相似的模式,因为一个不同的原因是有道理的。它看起来像这样:
//declare box, element, pos
function writeLayout(){
element.classList.add('is-foo');
}
window.addEventListener('scroll', ()=> {
box = element.getBoundingClientRect();
if(box.top > pos){
requestAnimationFrame(writeLayout);
}
});
使用此模式,您可以成功减少甚至删除布局颠簸。这个想法很简单:在滚动侦听器内部,您可以阅读布局并决定是否需要修改DOM,然后调用使用rAF修改DOM的函数。为什么这有用? rAF
确保您移动布局失效(在框架的位置)。这意味着在同一帧内调用的任何其他代码都可以在有效的布局上工作,并且可以使用超快速布局读取方法进行操作。
这种模式实际上非常棒,我建议使用以下辅助方法(用ES5编写):
/**
* @param fn {Function}
* @param [throttle] {Boolean|undefined}
* @return {Function}
*
* @example
* //generate rAFed function
* jQuery.fn.addClassRaf = bindRaf(jQuery.fn.addClass);
*
* //use rAFed function
* $('div').addClassRaf('is-stuck');
*/
function bindRaf(fn, throttle){
var isRunning, that, args;
var run = function(){
isRunning = false;
fn.apply(that, args);
};
return function(){
that = this;
args = arguments;
if(isRunning && throttle){return;}
isRunning = true;
requestAnimationFrame(run);
};
}
requestIdleCallback
来自API类似于rAF
但提供了完全不同的东西。它为您提供了帧内的一些空闲时段。 (通常是浏览器计算布局和完成绘制之后的点,但是在v-sync发生之前还有一些时间。)即使页面从用户视图延迟,也可能存在一些框架,其中浏览器是空转。虽然rIC
可以给你最多。 50毫秒。大多数情况下,您只需要0.5到10毫秒即可完成任务。由于调用帧生命周期rIC
回调中的这一点,您不应该更改DOM(为此使用rAF
。)
最后,使用scroll
限制rIC
侦听器进行延迟加载,无限滚动等等是非常有意义的。对于这些类型的用户界面,您甚至可以节制更多,并在其前面添加setTimeout
。 (所以你等待100毫秒,然后是rIC
)(debounce和throttle的生活示例。)
这是also an article about rAF
,其中包括两个图表,可能有助于理解框架生命周期内的不同点#34;。
发明了被动事件监听器以改善滚动性能。现代浏览器将页面滚动(滚动渲染)从主线程移动到合成线程。 (见https://hacks.mozilla.org/2016/02/smoother-scrolling-in-firefox-46-with-apz/)
但是有些事件会产生滚动,这可以通过脚本来防止(在主线程中发生,因此可以恢复性能提升)。
这意味着只要其中一个事件侦听器被绑定,浏览器就必须等待这些侦听器在浏览器计算滚动之前执行。这些事件主要是touchstart
,touchmove
,touchend
,wheel
,理论上在某种程度上keypress
和keydown
。 scroll
事件本身不这些事件之一。 scroll
事件没有默认操作,可以通过脚本阻止。
这意味着如果您未在preventDefault
,touchstart
,touchmove
和/或touchend
中使用wheel
,请始终使用被动事件听众,你应该没事。
如果您使用的是preventDefault,请检查是否可以使用CSS touch-action
属性替换它,或者至少在DOM树中将其替换为低级(例如,没有针对这些事件的事件委派)。对于wheel
个侦听器,您可以在mouseenter
/ mouseleave
上绑定/取消绑定它们。
如果发生任何其他事件:使用被动事件侦听器来提高性能是没有意义的。最重要的是要注意: scroll
事件无法取消,因此从不对scroll
使用被动事件监听器有意义。
如果无限滚动视图您不需要touchmove
,则只需要scroll
,因此被动事件监听器甚至不适用。
回答你的问题
setTimeout
+ requestIdleCallback
的组合,并对任何布局写入(DOM突变)使用rAF
。rAF
进行任何布局写入(DOM突变)。答案 1 :(得分:0)
我还提到了使用requestAnimationFrame不仅节流,而且完全替代了滚动侦听器的一些提法。
有人可以对此发表评论吗?我认为这是一种不好的做法,因为即使不滚动它也会继续循环。