IntersectionObserver简介

## 写在前面

在移动端,有个很重要的概念,叫做懒加载,适用于一些图片资源特别多,ajax数据特别多的页面中,经常会有动态加载数据的场景中,这个时候,我们通常是使用监听scroll或者使用setInterval来判断,元素是否进入视图,其中scroll由于其特别大的计算量,会有性能问题,而setInterval由于其有间歇期,也会出现体验问题。

## 1. 关于IntersectionObserver
浏览器的开发商,估计也发现了这个问题,所以在2016年初,chrome51率先提供了一个新的API,就是IntersectionObserver,它可以用来监听元素是否进入了设备的可视区域之内,而不需要频繁的计算来做这个判断。

毕竟是一个新兴的API,所以浏览器的支持性并不好,这里可以看看当前浏览器对于IntersectionObserver的支持性:[IntersectionObserver-canuse](http://caniuse.com/#search=IntersectionObserver)
![Alt text](//www.zhangyunling.com/study/2018/20180227/caniuse.png)

虽然当前受限于浏览器的支持性,该方法还不能用于生产环境中,但却不影响我们去先了解一下这个令人兴奋的API,所以在接下来的文章中,就先来一步步的看看,为我们带来了哪些好处吧。

## 2. API简介
API的调用非常简单:

“`
var observer = new IntersectionObserver(callback,options);
“`

IntersectionObserver支持两个参数:

  1. callback是当被监听元素的可见性变化时,触发的回调函数
  2. options是一个配置参数,可选,有默认的属性值

接下来,就看一个官方的示例代码:

“`
//初始化一个实例
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](https://wicg.github.io/IntersectionObserver/#example-dfb36139)

俗话说,纸上得来终觉浅,还是看个小示例吧:[第一场实战演练](//www.zhangyunling.com/study/2018/20180227/index-mobile-1.html)

## 3. callback和options

IntersectionObserver这个方法中,最重要的一个参数就是callback参数,如前面的示例中看到的,那么我们后面再来说另外一个参数,options吧。

现在就先来看一下options的参数:

options是可以选择设置的,即便我们不设置该参数,也会有一个默认的属性,这个默认的属性,就定义在了IntersectionObserver的原型链中,那么接下来看看有哪些默认的属性和方法:

“`
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的元素的绑定
“`

其中,rootrootMarginthresholds这三者是属性,说到这里,就是想引入前面调用的时候的options的取值。在IntersectionObserver的第二个参数options中,可以通过设置这三个属性,来改变IntersectionObserver实例的一些表现。

接下来我们就一个个的看一下它们的具体效果。

### 3.1 root参数

根元素,可视性以哪个root元素,作为参考系,如果不设置,那么可视区域,就是整个视图的区域,那么只要元素在该区域可视之后,就可以触发监听的回调函数。

俗话再说,事实胜于雄辩,再来实战吧:

[第二场实战演练(root设置为一个DIV)](//www.zhangyunling.com/study/2018/20180227/index-mobile-2.html)

第二场实战中,设置root为一个DIV元素,并添加了两个被监听的元素,其中,root内部的元素,可视性变化的时候,会触发callback,而root外部的元素,即便是一直在可视区域,也不会出现触发可视性的变化,这就是root的效果。

再做一个对比,如果不设置的时候:

[第三场实战演练(root为默认值)](//www.zhangyunling.com/study/2018/20180227/index-mobile-3.html)

root参与设置监听的区域,就比如上面的两个示例,如果给root设置了为一个DIV,那么所有被监听的目标元素,只有那些root元素的子元素,当其可见性变化时,才会触发该实例的回调函数。在该root元素之外的目标元素,可见性变化时,是无法被监听到的。

并且,还有一个问题,如果root设置为一个DIV,而这个DIV的高度,是高于设备的可视区域的,那么当滚动这个区域时,元素在没有还没有进入设备的可视区域的时候,已经进入了root的可视区域了,那么依然也会触发可视性改变的回调函数,在本小节上面的两个示例中,都可以测试出该种状态。

### 3.2 rootMargin属性

关于这个属性,我们可以理解为更精确的配置参数,它是设置在root元素上的一个margin属性,其使用方法与CSS中的margin属性是完全相同的,虽然这个命名为rootMargin,也是设置在root属性上的,但真正起作用是作用在元素上的

如下图所示:
![Alt text](//www.zhangyunling.com/study/2018/20180227/img-1.png)

如图,root元素就是图中的黑色区域,给它设置一个rootMargin:10px的属性,就是如图中,黑色区域,和边框之间的间隔区域。

假设现在监听元素ele(假设为上图中,红色元素区域)的可视性变化,那么如果我们不设置rootMargin的话,只有当ele元素,进入图中黑色区域的时候,其可视性才会改变,而这里,我们设置了rootMagrin的值,如图所示,当ele进入到边框内部,还没有进入到黑色区域时,就可以触发可视性变化的。

或者也可以这样理解,root元素,多了一个margin属性,如果没有这个margin属性,ele元素只有与root元素开始交叉时才会触发可视性的变化,而这个rootMargin属性的话,就是当ele元素与root元素的外边距交叉时,就会触发ele元素的可视性变化。

那么rootMagrin的好处在哪里呢?可以理解为,懒加载的预加载,比如图片的懒加载,不是等图片元素进入可视区域之后,才去加载,而是检测到,离进入可视区域,在一定范围内部的时候,就提前加载(叫做预加载吧),这样,当真的进入视图之内,说不定已经加载好了,可以大大的提升整个产品的体验。

[第四场实战演练](//www.zhangyunling.com/study/2018/20180227/index-mobile-5.html)

虽然说,rootMargin的设置方法,与CSS中的margin属性设置方法完全相同,但是其设置的值,却不完全相同,比如,rootMargin属性就不能支持“em”,“rem”等单位的设置。所以设置时,尽量使用一些基本的单位,比如“px”,”%“等。

### 3.3:threshold属性

在前面的示例中,每个元素只有在刚出现和刚离开的时候(change.intersectionRatio=0的变化时),才会触发可视性的变化,如果只是这样的话,那该方法就显得过于死板了,一点都不灵活。

所以,在初始化设置参数时,还可以传入一个threshold的参数,用来设置当change.intersectionRatio属性,达到指定值时,也会触发回调函数。

threshold的默认值是:[0],即只有在开始进入,或者是完全离开视图区域时,才会触发。

这里有个很疑问的地方,为何在实例属性中,是thresholds的属性,而在设置时,却是设置threshold的值呢,比如设置和读取:

“`
var observer = new IntersectionObserver(_observer, {
root : $(“#containter”)[0],
threshold:[0,0.25,0.5,0.75]
//设置时,不是个复数
});

//查看实例属性时,却是复数。
observer.thresholds
“`

继续看DEMO吧:[第五场实战演练(threshold属性)](//www.zhangyunling.com/study/2018/20180227/index-pc-6.html)

注意:虽然,上述的三个属性,都是属于实例中的属性,但是这三个属性,只是可读的,当实例化之后,这三个属性就是不可更改的。有兴趣的可以自己试试。

### 3.4 回调函数
前面把options的参数,介绍了一下,接下来再来看看callback能为我们提供些什么信息吧。

可以看第二小节中的示例代码,可以看到,在callback中,是传入一个数组,数组中的每一个元素,都代表一个可视性变化的DOM元素,它包含该元素的一些基本信息和变化的信息。

看如下的代码:

“`
//初始化一个实例
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);
}
}, {});
“`

参数的含义,在本篇开头的地方,示例也在前面引入过来,这里继续引入前面的一个示例吧:[再看第一场实战演练](//www.zhangyunling.com/study/2018/20180227/index-mobile-1.html)。

其中,有三个参数是用来获取位置信息的,rootBoundsroot元素的位置信息),boundingClientRecttarget元素的位置信息),intersectionRect(进入交叉显示区域的位置信息,与intersectionRatio属性有关)。如果要更好的理解,需要看一下前面的示例,在控制台对比这三个数据,才能更好的理解。

## 4. 其他

前面我们说的都是一些基础的元素,基础的属性,那么如果设置了positionmargindisplayvisibilityclip等属性呢?对于元素的可视性,会有什么影响吗?

这个希望有兴趣了解的,可以自己去尝试一把,来一次实战如何?

IntersectionObserver实例是一个异步的实例,它只有在浏览器空闲的状态下才会触发,如果浏览器当前的事件队列中,有一系列的回调函数正在等待处理,该方法是不会被执行到的,只有当浏览器的事件队列为空,浏览器在空闲的时候,才会执行该方法。

就异步的处理方式来看,该APIrequestIdleCallbackAPI,效果是一样的,都是在浏览器空闲的时候,才会被执行,他们与计时器的原理是不一样的,计时器的原理是在指定的时间之后,就把处理函数直接推入到执行队列中去,而这两个方法,就算已经被触发了(或者说,调用过来,依然不会出现在函数执行队列中)。

这是浏览器的一个优化,考虑用户体验的同时,也在考虑浏览器的性能消耗。

## 5. 应用

1. 预加载(滚动加载,无限加载)
2. 懒加载(图片后加载)
3. TAB滚动跟随

暂时能想到的应用场景,就是上面提到的这些个场景

关于这些应用的实战,就靠你啦,去实战一下吧。

## 6. 总结
在当前判断可视性的方法,基本就是监听scroll事件,或者是计时器循环判断来做这个判断,但是由于其高频的计算频率,会导致浏览器性能的损失,尤其是,如果一个同一个页面中,有多个地方,需要这样的判断,那么就需要绑定多个scroll事件,或者有多个计时器在轮询的话,那么对性能的损失就更为客观了。

虽然现在的浏览器性能一直在增强,但是也有更多的消耗性能的比较炫的技术在产生,它们依然在占据着浏览器的大量的计算内存,所以,尽量在可以节省性能的时候,就节省一下性能吧。

而该方法给我们提供了一个更简单直接,性能更好的解决方案,希望以后的浏览器,可以越来越广泛的支持吧。

本篇到此为止,感谢阅读。

如果您发现文中有描述错误或者不当的地方,请留言指出,不胜感激,谢谢!

本文地址:http://www.zhangyunling.com/?p=811

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>