网页开发时,常常需要面临如下需求:

  • 图片懒加载——当图片滚动到可见时才进行加载
  • 内容无限滚动——也就是用户滚动到接近内容底部时直接加载更多,而无需用户操作翻页,给用户一种网页可以无限滚动的错觉
  • 检测广告的曝光情况——为了计算广告收益,需要知道广告元素的曝光情况
  • 在用户看见某个区域时执行任务或播放动画

observe.gif
上图的蓝色方块不断滚动,顶部会提示它的可见性。
传统的实现方法是,监听到scroll事件后,调用目标元素(绿色方块)的getBoundingClientRect()方法,得到它对应于视口左上角的坐标,再判断是否在视口之内。这种方法的缺点是,由于scroll事件密集发生,计算量很大网页需要不停的重绘,容易造成性能问题。

Intersection Observer API提供了一种异步检测目标元素与祖先元素或 viewport 相交情况变化的方法。Intersection Observer API 会注册一个回调函数,每当被监视的元素进入或者退出另外一个元素时(或者 viewport ),或者两个元素的相交部分大小发生变化时,该回调方法会被触发执行。这样,我们网站的主线程不需要再为了监听元素相交而辛苦劳作,浏览器会自行优化元素相交管理。

API

它的用法非常简单。

  1. var io = new IntersectionObserver(callback, option);

上面代码中,IntersectionObserver是浏览器原生提供的构造函数,接受两个参数:callback是可见性变化时的回调函数,option是配置对象(该参数可选)。

构造函数的返回值是一个观察器实例。实例的 observe 方法可以指定观察哪个 DOM 节点。

  1. io.disconnect() //阻止观察任何目标元素
  2. io.observe() //开始观察目标元素
  3. io.takeRecords()//返回所有观察到的目标的对象
  4. io.unobserve()//停止观察目标元素

上面代码中,observe的参数是一个 DOM 节点对象。如果要观察多个节点,就要多次调用这个方法。

  1. io.observe(elemA);
  2. io.observe(elemB);

callback回调函数

目标元素的可见性变化时,就会调用观察器的回调函数callback。
callback一般会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)。

  1. var io = new IntersectionObserver(
  2. entries => {
  3. console.log(entries);
  4. }
  5. );

上面代码中,回调函数采用的是箭头函数的写法。callback函数的参数(entries)是一个数组,每个成员都是一个IntersectionObserverEntry对象。举例来说,如果同时有两个被观察的对象的可见性发生变化,entries数组就会有两个成员。

IntersectionObserverEntry 对象

IntersectionObserverEntry对象提供目标元素的信息,一共有七个属性。

  1. {
  2. time: 3893.92,
  3. rootBounds: ClientRect {
  4. bottom: 920,
  5. height: 1024,
  6. left: 0,
  7. right: 1024,
  8. top: 0,
  9. width: 920
  10. },
  11. boundingClientRect: ClientRect {
  12. // ...
  13. },
  14. intersectionRect: ClientRect {
  15. // ...
  16. },
  17. intersectionRatio: 0.54,
  18. isIntersecting: false,
  19. target: element
  20. }

每个属性的含义如下。

  • time:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒
  • rootBounds:根元素的矩形区域的信息,getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回null
  • boundingClientRect:目标元素的矩形区域的信息
  • intersectionRect:目标元素与视口(或根元素)的交叉区域的信息
  • intersectionRatio:目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于等于0
  • isIntersecting 一个布尔值,指向目标元素是否已转换为相交状态(true)还是已脱离相交状态(false)。
  • target:被观察的目标元素,是一个 DOM 节点对象

option 对象

IntersectionObserver构造函数的第二个参数是一个配置对象。它可以设置以下属性。

root

指定根(root)元素,用于检查目标的可见性。必须是目标元素的父级元素。如果未指定或者为null,则默认为浏览器视窗。

rootMargin

根(root)元素的外边距。类似于 CSS 中的 margin 属性,比如 “10px 20px 30px 40px” (top, right, bottom, left)。如果有指定root参数,则rootMargin也可以使用百分比来取值。该属性值是用作root元素和target发生交集时候的计算交集的区域范围,使用该属性可以控制root元素每一边的收缩或者扩张。默认值为0。

threshold

可以是单一的number也可以是number数组,target元素和root元素相交程度达到该值的时候IntersectionObserver注册的回调函数将会被执行。如果你只是想要探测当target元素的在root元素中的可见性超过50%的时候,你可以指定该属性值为0.5。如果你想要target元素在root元素的可见程度每多25%就执行一次回调,那么你可以指定一个数组[0, 0.25, 0.5, 0.75, 1]。默认值是0(意味着只要有一个target像素出现在root元素中,回调函数将会被执行)。该值为1.0含义是当target完全出现在root元素中时候 回调才会被执行。

How intersection is calculated — 交集的计算

所有区域均被Intersection Observer API 当做一个矩形看待。如果元素是不规则的图形也将会被看成一个包含元素所有区域的最小矩形,相似的,如果元素发生的交集部分不是一个矩形,那么也会被看作是一个包含他所有交集区域的最小矩形。
这个有助于理解IntersectionObserverEntry 的属性,IntersectionObserverEntry用于描述target和root的交集。

The intersection root and root margin

在我们开始跟踪target元素和容器元素之前,我们要先知道什么是容器(root)元素。容器元素又称为intersection root, 或 root element.这个既可以是target元素祖先元素也可以是指定null则使用浏览器视口做为容器(root)。 用作描述intersection root 元素边界的矩形可以使用root margin来调整矩形大小,即rootMargin属性,在我们创建IntersectionObserver对象的时候使用。rootMargin的属性值将会做为margin偏移值添加到intersection root 元素的对应的margin位置,并最终形成root 元素的矩形边界。

Thresholds

IntersectionObserver API并不会每次在元素的交集发生变化的时候都会执行回调。相反它使用了thresholds参数。当你创建一个observer的时候,你可以提供一个或者多个number类型的数值用来表示target元素在root元素的可见程序的百分比,然后,API的回调函数只会在元素达到thresholds规定的阈值时才会执行。

例如,当你想要在target在root元素中中的可见性每超过25%或者减少25%的时候都通知一次。你可以在创建observer的时候指定thresholds属性值为[0, 0.25, 0.5, 0.75, 1],你可以通过检测在每次交集发生变化的时候的都会传递回调函数的参数”IntersectionObserverEntry.isIntersecting”的属性值来判断target元素在root元素中的可见性是否发生变化。如果isIntersecting 是 true,target元素的至少已经达到thresholds属性值当中规定的其中一个阈值,如果是false,target元素不在给定的阈值范围内可见。

参考地址:https://developer.mozilla.org/zh-CN/docs/Web/API/Intersection_Observer_API
参考地址:http://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html