我们知道,通常建议去掉滚动侦听器,以便在用户滚动时UX更好.
但是,我经常找到像Paul Lewis这样有影响力的人推荐使用的图书馆和文章requestAnimationFrame
.但是,随着Web平台的快速发展,某些建议可能会随着时间的推移而被弃用.
我看到的问题是处理滚动事件有很多不同的用例,例如构建视差网站,或处理无限滚动和分页.
我看到3个可以在UX方面有所作为的主要工具:
requestAnimationFrame
requestIdleCallback
被动事件监听器
所以,我想知道,每个用例(我只有2个,但你可以拿出其他的),我现在应该使用什么样的工具来获得非常好的滚动体验?
更确切地说,我的主要问题将与无限滚动视图和分页(通常不必触发视觉动画,但我们想要一个良好的滚动体验)更相关,是否更好地用+被动滚动requestAnimationFrame
的组合替换requestIdleCallback
事件处理程序 我也想知道什么时候requestIdleCallback
用于调用API或处理API响应以使滚动执行得更好,或者浏览器可能已经为我们处理了什么?
虽然这个问题有点老了,但我想回答它,因为我经常看到脚本,其中很多这些技术都被误用了.
一般而言,所有你问的工具(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
是给你的东西.这意味着用某种东西限制某些东西没有任何意义,这已经被每个定义完全相同的东西所限制.
在实践中,您可以通过添加a检查我刚刚说的内容,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编写):
/** * From /sf/ask/17360801/ * * @param {Function} fn Callback function * @param {Boolean|undefined} [throttle] Optionally throttle callback * @return {Function} Bound 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; var that; var 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
在其前面添加一个.(所以你做100ms等待然后a rIC
)(去抖动和油门的生活例子.)
这里还有一篇文章rAF
,其中包括两个可能有助于理解"框架生命周期"内部不同点的图表.
发明了被动事件监听器以改善滚动性能.现代浏览器将页面滚动(滚动渲染)从主线程移动到合成线程.(见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
用于您的事件侦听器并rAF
用于任何布局写入(DOM突变).
对于即时效果仍然rAF
用于任何布局写入(DOM突变).