昨天奇舞周刊推了一个“五分钟 get 你也许不知道的前端新特性”,里面有个关于lazy-load的东西:
IntersectionObserver
交叉观察器。
原文戳我!~
在追踪DOM元素进入可视区的时候,你可能想要在刚好的时间对图片进行懒加载,因为你需要知道用户是真的看到了某个确定的广告位。介个时候吧,可以监听scroll事件或者用一个周期性定时器在这个元素上调用getBoundingClientRect()
。但是!这种方法实在忒慢了,因为每次调用getBoundingClientRect()
的时候,浏览器会被强制对整个页面进行重排、并且产生大量的垃圾。当你知道你的网站被嵌入一个iframe而你想知道用户什么时候能看到某个元素的时候,这件事就有点儿坑爹了:单源模型和浏览器不会让你接近任何嵌在iframe当中的数据的。这对于被频繁嵌在iframe中加载的东西(拿广告来说)是个共性问题。
设计IntersectionObserver
是为了让这个可见度测试更加有效,并且chrome51+已经支持了。IntersectionObserver
会让你知道一个被观察的元素什么时候进入或离开可视区。
如何创建一个IntersectionObserver
它的API很小,举个栗子??就知道了:
var io = new IntersectionObserver(
entries => {
console.log(entries);
},
{
/* Using default options. Details below */
}
);
// Start observing an element
io.observe(element);
// Stop observing an element
// io.unobserve(element);
// Disable entire IntersectionObserver
// io.disconnect();
默认情况下,元素在出现和离开视口区的时候都会调用callback。
如果需要观察多个元素时,使用一个IntersectionObserver
接口并多次调用observe()
,这种方式是爸爸们比较欣慰的。
entries
参数是向callback当中传入的一组IntersectionObserverEntry
对象数组,每个IntersectionObserverEntry
对象包含着相应被观察元素的信息,叫作“更新交叉点数据(什么鬼)”(看下面结构就知道了)。
??[IntersectionObserverEntry]
time: 3893.92
??rootBounds: ClientRect
bottom: 920
height: 1024
left: 0
right: 1024
top: 0
width: 920
??boundingClientRect: ClientRect
// ...
??intersectionRect: ClientRect
// ...
intersectionRatio: 0.54
??target: div#observee
// ...
rootBounds
是在根元素(默认就是viewport)矩形区域的信息,调用getBoundingClientRect()
的返回值;
boundingClientRect
是在目标元素(默认就是viewport),矩形区域的信息,调用getBoundingClientRect()
的返回值;
intersectionRect
是目标元素与根元素交叉区域的信息,并且能清楚地告诉你目标元素的哪个部分是可见的。
intersectionRatio
目标元素的可见比例,密切相关的一个东西,它能告诉你元素当中有多大一部分是可见的(下图)。有了这个信息,你可以有效地实现一些功能,比如当资源在屏幕上可见之前刚好加载出来。
IntersectionObservers
是异步传递数据的,同时callback会运行在主线程当中。此外,规范当中说了,IntersectionObservers
的实现应该使用requestIdleCallback()
。这就意味着你所提供的callback的调用是低优先级的,会在空闲时间进行。这是一个有意识的设计决定。
滚动的div
我自己并不热爱在一个元素内进行滚动,但在此先不做评论,对IntersectionObservers
也是。
options
对象采用根选项,可以让你去定义一个viewport的替代品作为你的根元素。记住这一点很重要:根元素是所有观察元素的祖先!
交叉所有的元素
永远不要酱紫!对于你用户的CPU周期来说这是不过脑子的用法。想象一个无穷无尽的滚动条:在这个剧情当中,向DOM当中添加哨兵并且(循环地)观察他们是明智的。你需要给滚动条当中的最后一个节点添加一个哨兵。当那个哨兵出现在视窗当中,你可以用callback去加载数据、创建下个元素、加在DOM上,并相应地重新摆放哨兵的位置。如果你适当地重新使用了哨兵,就不需要额外调用observe()
了,现有的IntersectionObservers
会持续运行下去。
更多更新(???)
之前提到过,当目标元素部分进入viewport或是它在其他时间离开viewport时,callback会被触发一次。在这种方式下IntersectionObservers
会回答你这样的问题“元素X在视窗区么?”,但有时候酱紫是不够的。
下面threshold
就要登场了:这个东西允许你定义一组intersectionRatio
门槛,你的callback会在每次intersectionRatio
越过其中一个值的时候被调用一次。它的默认值是[0]
,如果我们设置这样一组值:[0, 0.25, 0.5, 0.75, 1]
,我们会在元素的每四分之一出现的时候被告之。(拜说了看下图吧)
还有其他选项么?
到现在为止,上面列出的选项里面只剩一个了:rootMargin
,允许你为根元素指明margin,有效地允许你增大或缩小交叉区域。这些margin跟CSS写法一样:"10px 20px 30px 40px"
相对应的是上、右、下、左的margin值。总的来说,IntersectionObservers
的选项结构是提供如下选项的:
new IntersectionObserver(entries => {/* … */}, {
// The root to use for intersection.
// If not provided, use the top-level document’s viewport.
root = null,
// Same as margin, can be 1, 2, 3 or 4 components, possibly negative lengths.
// If an explicit root element is specified, components may be percentages of the
// root element size. If no explicit root element is specified, using a percentage
// is an error.
rootMargin = "0px",
// Threshold(s) at which to trigger callback, specified as a ratio, or list of
// ratios, of (visible area / total area) of the observed element (hence all
// entries must be in the range [0, 1]). Callback will be invoked when the visible
// ratio of the observed element crosses a threshold in the list.
threshold = [0],
});
iframe魔法(哈?)
IntersectionObservers
专门为广告服务和社交网络设计了小组件,它们经常使用iframe,而且知道它们是否出现在视窗中是有好处的。如果一个iframe观察着它的一个元素,不管是滚动iframe还是滚动包含着iframe的window,都会在合适的时间触发callback。然而对于后一种情况,rootBounds
将被置为null以避免跨越原始值泄露了数据。
有什么是IntersectionObservers不是的
有些事儿你得知道,IntersectionObservers
有意地既不是像素完美也不是低延迟,用它来尝试实现像滚动相关的动画的话基本就是废物一个,严格讲,数据在你用它的时候是过期的。explainer 有关于原始用例的更多细节。
callback当中能做多少事儿?
在callback当中花费太多时间的话会让你的app滞后,所有常见做法都适用。
向前走,和你的元素相交(什么鬼)
浏览器对IntersectionObservers
的支持还挺弱的,所以目前来看这东西不会在所有地方都好好生效的。与此同时,一个polyfill正在WICG的存储库中生效。显然,你用这个polyfill不会获得像native实现能带给你的性能优化。
你现在就能在Chrome Canary使用IntersectionObservers
啦~ 不管你遇到什么,告诉我们。
奇舞周刊推的东西还是挺有营养的。。。
突然想起几天前被三百六的一哥们儿在一面的时候整整面了俩小时!两个小时?。?!那个一楼的面试间,贼冷好么!就当我觉得这哥哥肯定是说了算的吧?面俩小时是不是意味着直接HR了?偷笑ing的时候,人家淡定得说了说“先吃午饭去吧,下午接着二面”。( ⊙ o ⊙ )当时我就凌乱了,走出楼的时候连灵魂都在大风中骄傲放纵着。。。
三百六啊三百六,你是觉得你虐我千百遍,我还能待你如初恋么??~