写在前面
在移动端,有个很重要的概念,叫做懒加载,适用于一些图片资源特别多,ajax
数据特别多的页面中,经常会有动态加载数据的场景中,这个时候,我们通常是使用监听scroll
或者使用setInterval
来判断,元素是否进入视图,其中scroll
由于其特别大的计算量,会有性能问题,而setInterval
由于其有间歇期,也会出现体验问题。
1. 关于IntersectionObserver
浏览器的开发商,估计也发现了这个问题,所以在2016年初,chrome51率先提供了一个新的API
,就是IntersectionObserver
,它可以用来监听元素是否进入了设备的可视区域之内,而不需要频繁的计算来做这个判断。
毕竟是一个新兴的API
,所以浏览器的支持性并不好,这里可以看看当前浏览器对于IntersectionObserver
的支持性:IntersectionObserver-canuse
虽然当前受限于浏览器的支持性,该方法还不能用于生产环境中,但却不影响我们去先了解一下这个令人兴奋的API
,所以在接下来的文章中,就先来一步步的看看,为我们带来了哪些好处吧。
2. API简介
该API
的调用非常简单:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
var observer = new IntersectionObserver(callback,options);
IntersectionObserver
支持两个参数:
-
callback
是当被监听元素的可见性变化时,触发的回调函数
options
是一个配置参数,可选,有默认的属性值
接下来,就看一个官方的示例代码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
//初始化一个实例
var observer = new IntersectionObserver(changes => {
for (const change of changes) { console.log(change.time); // Timestamp when the change occurred // 当可视状态变化时,状态发送改变的时间戳 // 对比时间为,实例化的时间, // 比如,值为1000时,表示在IntersectionObserver实例化的1秒钟之后,触发该元素的可视性变化 console.log(change.rootBounds); // Unclipped area of root // 根元素的矩形区域信息,即为getBoundingClientRect方法返回的值 console.log(change.boundingClientRect); // target.boundingClientRect() // 目标元素的矩形区域的信息 console.log(change.intersectionRect); // boundingClientRect, clipped by its containing block ancestors, // and intersected with rootBounds // 目标元素与视口(或根元素)的交叉区域的信息 console.log(change.intersectionRatio); // Ratio of intersectionRect area to boundingClientRect area // 目标元素的可见比例,即intersectionRect占boundingClientRect的比例, // 完全可见时为1,完全不可见时小于等于0 console.log(change.target); // the Element target // 被观察的目标元素,是一个 DOM 节点对象 // 当前可视区域正在变化的元素 } }, {}); // Watch for intersection events on a specific target Element. // 对元素target添加监听,当target元素变化时,就会触发上述的回调 observer.observe(target); // Stop watching for intersection events on a specific target Element. // 移除一个监听,移除之后,target元素的可视区域变化,将不再触发前面的回调函数 observer.unobserve(target); // Stop observing threshold events on all target elements. // 停止所有的监听 observer.disconnect();
详情请参考:IntersectionObserver
俗话说,纸上得来终觉浅,还是看个小示例吧:第一场实战演练
3. callback和options
在IntersectionObserver
这个方法中,最重要的一个参数就是callback
参数,如前面的示例中看到的,那么我们后面再来说另外一个参数,options
吧。
现在就先来看一下options
的参数:
options
是可以选择设置的,即便我们不设置该参数,也会有一个默认的属性,这个默认的属性,就定义在了IntersectionObserver
的原型链中,那么接下来看看有哪些默认的属性和方法:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
disconnect()
// function,没有参数。
// 停止实例的继续监听,在前面的callback回调函数中,已经有看到了
observe(target)
// function,传入一个参数,原生的DOM对象,一个将要被监听的DOM元素
// 添加一个监听元素,
root
// DOM元素,但默认值为null,也就是视口区域 // 表示监听的可视区域为整个视口,也就是浏览器的可视区域 // 如果设置了DOM元素,那么视口就变为该元素(即,就算元素在屏幕的可视区域,但是不在该DOM元素的可视区域,仍然不会触发可视性变化) rootMargin // 类似于css的margin属性,可以设置四个属性。 // 该属性默认值为0px 0px 0px 0px,如果设置之后,会影响触发回调的时间 takeRecords // 该属性,目前没有找到是干嘛的,返回一个数组,而且一直为空数组 thresholds // Array // 默认值为[0], // 当元素的进入可视区域的百分比达到这个参数的设置值得时候 // 就会触发IntersectionObserver实例中的callback的回调函数 // 具体后面再看啦 unobserve(target) // 解除target的元素的绑定
其中,root
,rootMargin
,thresholds
这三者是属性,说到这里,就是想引入前面调用的时候的options
的取值。在IntersectionObserver
的第二个参数options
中,可以通过设置这三个属性,来改变IntersectionObserver
实例的一些表现。
接下来我们就一个个的看一下它们的具体效果。
3.1 root参数
根元素,可视性以哪个root
元素,作为参考系,如果不设置,那么可视区域,就是整个视图的区域,那么只要元素在该区域可视之后,就可以触发监听的回调函数。
俗话再说,事实胜于雄辩,再来实战吧:
第二场实战演练(root设置为一个DIV)
第二场实战中,设置root
为一个DIV
元素,并添加了两个被监听的元素,其中,root
内部的元素,可视性变化的时候,会触发callback
,而root
外部的元素,即便是一直在可视区域,也不会出现触发可视性的变化,这就是root
的效果。
再做一个对比,如果不设置的时候:
第三场实战演练(root为默认值)
root
参与设置监听的区域,就比如上面的两个示例,如果给root
设置了为一个DIV
,那么所有被监听的目标元素,只有那些root
元素的子元素,当其可见性变化时,才会触发该实例的回调函数。在该root
元素之外的目标元素,可见性变化时,是无法被监听到的。
并且,还有一个问题,如果root
设置为一个DIV,而这个DIV的高度,是高于设备的可视区域的,那么当滚动这个区域时,元素在没有还没有进入设备的可视区域的时候,已经进入了root
的可视区域了,那么依然也会触发可视性改变的回调函数,在本小节上面的两个示例中,都可以测试出该种状态。
3.2 rootMargin属性
关于这个属性,我们可以理解为更精确的配置参数,它是设置在root
元素上的一个margin
属性,其使用方法与CSS
中的margin
属性是完全相同的,虽然这个命名为rootMargin
,也是设置在root
属性上的,但真正起作用是作用在元素上的
如图,root
元素就是图中的黑色区域,给它设置一个rootMargin:10px
的属性,就是如图中,黑色区域,和边框之间的间隔区域。
假设现在监听元素ele
(假设为上图中,红色元素区域)的可视性变化,那么如果我们不设置rootMargin
的话,只有当ele
元素,进入图中黑色区域的时候,其可视性才会改变,而这里,我们设置了rootMagrin
的值,如图所示,当ele进入到边框内部,还没有进入到黑色区域时,就可以触发可视性变化的。
或者也可以这样理解,root
元素,多了一个margin
属性,如果没有这个margin
属性,ele
元素只有与root
元素开始交叉时才会触发可视性的变化,而这个rootMargin
属性的话,就是当ele
元素与root
元素的外边距交叉时,就会触发ele
元素的可视性变化。
那么rootMagrin
的好处在哪里呢?可以理解为,懒加载的预加载,比如图片的懒加载,不是等图片元素进入可视区域之后,才去加载,而是检测到,离进入可视区域,在一定范围内部的时候,就提前加载(叫做预加载吧),这样,当真的进入视图之内,说不定已经加载好了,可以大大的提升整个产品的体验。
第四场实战演练
虽然说,rootMargin
的设置方法,与CSS
中的margin
属性设置方法完全相同,但是其设置的值,却不完全相同,比如,rootMargin
属性就不能支持“em
”,“rem
”等单位的设置。所以设置时,尽量使用一些基本的单位,比如“px
”,”%
“等。
3.3:threshold属性
在前面的示例中,每个元素只有在刚出现和刚离开的时候(change.intersectionRatio=0的变化时),才会触发可视性的变化,如果只是这样的话,那该方法就显得过于死板了,一点都不灵活。
所以,在初始化设置参数时,还可以传入一个threshold
的参数,用来设置当change.intersectionRatio
属性,达到指定值时,也会触发回调函数。
threshold
的默认值是:[0]
,即只有在开始进入,或者是完全离开视图区域时,才会触发。
这里有个很疑问的地方,为何在实例属性中,是thresholds
的属性,而在设置时,却是设置threshold
的值呢,比如设置和读取:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
var observer = new IntersectionObserver(_observer, {
root : $(“#containter”)[0],
threshold:[0,0.25,0.5,0.75] //设置时,不是个复数 }); //查看实例属性时,却是复数。 observer.thresholds
继续看DEMO吧:第五场实战演练(threshold属性)
注意:虽然,上述的三个属性,都是属于实例中的属性,但是这三个属性,只是可读的,当实例化之后,这三个属性就是不可更改的。有兴趣的可以自己试试。
3.4 回调函数
前面把options
的参数,介绍了一下,接下来再来看看callback
能为我们提供些什么信息吧。
可以看第二小节中的示例代码,可以看到,在callback
中,是传入一个数组,数组中的每一个元素,都代表一个可视性变化的DOM
元素,它包含该元素的一些基本信息和变化的信息。
看如下的代码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
//初始化一个实例
var observer = new IntersectionObserver(changes => {
for (const change of changes) { console.log(change.time); console.log(change.rootBounds); console.log(change.boundingClientRect); console.log(change.intersectionRect); console.log(change.intersectionRatio); console.log(change.target); } }, {});
参数的含义,在本篇开头的地方,示例也在前面引入过来,这里继续引入前面的一个示例吧:再看第一场实战演练。
其中,有三个参数是用来获取位置信息的,rootBounds
(root
元素的位置信息),boundingClientRect
(target
元素的位置信息),intersectionRect
(进入交叉显示区域的位置信息,与intersectionRatio
属性有关)。如果要更好的理解,需要看一下前面的示例,在控制台对比这三个数据,才能更好的理解。
4. 其他
前面我们说的都是一些基础的元素,基础的属性,那么如果设置了position
,margin
,display
,visibility
,clip
等属性呢?对于元素的可视性,会有什么影响吗?
这个希望有兴趣了解的,可以自己去尝试一把,来一次实战如何?
IntersectionObserver
实例是一个异步的实例,它只有在浏览器空闲的状态下才会触发,如果浏览器当前的事件队列中,有一系列的回调函数正在等待处理,该方法是不会被执行到的,只有当浏览器的事件队列为空,浏览器在空闲的时候,才会执行该方法。
就异步的处理方式来看,该API
与requestIdleCallback
的API
,效果是一样的,都是在浏览器空闲的时候,才会被执行,他们与计时器的原理是不一样的,计时器的原理是在指定的时间之后,就把处理函数直接推入到执行队列中去,而这两个方法,就算已经被触发了(或者说,调用过来,依然不会出现在函数执行队列中)。
这是浏览器的一个优化,考虑用户体验的同时,也在考虑浏览器的性能消耗。
5. 应用
- 预加载(滚动加载,无限加载)
- 懒加载(图片后加载)
- TAB滚动跟随
暂时能想到的应用场景,就是上面提到的这些个场景
关于这些应用的实战,就靠你啦,去实战一下吧。
6. 总结
在当前判断可视性的方法,基本就是监听scroll
事件,或者是计时器循环判断来做这个判断,但是由于其高频的计算频率,会导致浏览器性能的损失,尤其是,如果一个同一个页面中,有多个地方,需要这样的判断,那么就需要绑定多个scroll
事件,或者有多个计时器在轮询的话,那么对性能的损失就更为客观了。
虽然现在的浏览器性能一直在增强,但是也有更多的消耗性能的比较炫的技术在产生,它们依然在占据着浏览器的大量的计算内存,所以,尽量在可以节省性能的时候,就节省一下性能吧。
而该方法给我们提供了一个更简单直接,性能更好的解决方案,希望以后的浏览器,可以越来越广泛的支持吧。
本篇到此为止,感谢阅读。
如果您发现文中有描述错误或者不当的地方,请留言指出,不胜感激,谢谢!
本原文地址:http://www.zhangyunling.com/?p=811