JS API Intersection Observer与懒加载

avatar
Mofei Zhu

大名鼎鼎的Mapbox最近发布了Global View,无论是在Web还是Mobile上都可以爽快顺滑的运行。

Mapbox

于是第一时间更换了自己博客上的地图,由于这个地图在我的博客上并不是处于首屏的位置,考虑到部分手机用户的感受,还是决定对这个球进行懒加载,即页面快滚动到地球位置时再去初始化。

这样问题成功的变成了我们经常遇到的懒加载的问题,传统的做法是通过监听scroll事件,然后使用类似getBoundingClientRect()方法获取该对象的坐标,再进行各种计算来判断目标元素是否出现在了视口中。但是由于scroll会被频繁触发,以及getBoundingClientRect()方法的执行也是在浏览器的主线程上运行的,因此很容易出现性能问题,并且也不是那么的优雅。这种情况下我们的主角Intersection Observer API闪亮登场!

1. 初认 Intersection Observer API

Intersection Observer API 整体还是比较简洁并且相对来说比较容易理解的,它由2个部分组成 观察者 以及 被观察元素组成。在指定观察者被观察元素的关系之后,观察者会密切关注被观察元素,并且在达到一定条件时候(通过观察者的options配置)调用回调函数。

1.1 IntersectionObserver

我们可以通过 new IntersectionObserver(callback, options) 初始化一个观察者,例如:

// 初始化
let options = {
  root: document.querySelector('#box'),
  rootMargin: '0px',
  threshold: 1.0
}

const observer = new IntersectionObserver(callback, options);

// 监听元素
const el = document.querySelector('#target') // 被监听的元素
observer.observe(el) // 开始监听元素

1.1.1 options

options 是配置参数,主要告诉观察者什么时候应该触发回调,其中:

  • root 是观察者元素,必须是被观察元素的父级元素,如果未指定,则默认是浏览器窗口。
  • rootMargin 和CSS的margin 类似,可以设置为诸如 10px, 10px 20px 10px 20px的值,默认为0
  • threshold 用来控制当被观察元素和观察者视口相交到什么程度时候触发回调。可以是一个0-1之间的一个数值,也可以是多个0-1之间数字组成的数组,如[0.1,0.5,1.0] 。具体可以参见在下文中的电梯例子。 开始我们的电梯的例子,某个楼层的电梯的门(这里对应的是options中的root)就相当于是一个观察者,而电梯的箱体就相当于一个被观察元素。我们作为电梯的开发者,可以让电梯按照我们的需求运行,比如电梯刚到达这一层的时候(rootMargin)响铃提示等待的用户,电梯完全到达该楼层(threshold)时候打开电梯的门。

图一:rootrootMargin 示例

root & rootMargin

图二:对于上行的电梯,当顶部到达电梯门的底部的时候threshold为0 (这时候电梯门千万不能打开,否则..),当电梯完全和电梯门重叠的时候threshold为1(此时电梯门就可以放心的打开了... 咳咳,是吗?)*

threshold

图三:如果threshold 为数组,将会在任意一个值满足条件时触发回调

threshold

1.1.2 callback

callback方法会在以下情况下被触发:

  1. 被观察者进入或者离开视口时被执行。
  2. 在绑定被观察元素时触发,请尤其注意这种初始化绑定时候的触发,并通过IntersectionObserverEntry.isIntersecting检查被观察元素是否在视窗中。

callback方法接收2个参数 entriesobserver,其中:

  1. entries是相关被观察元素(IntersectionObserverEntry)的实例集合
  2. observer观察者(IntersectionObserver)的实例

1.1.3 IntersectionObserverEntry

IntersectionObserverEntry (被观察元素实例)包含以下只读属性:

  • boundingClientRect: 返回包含目标元素的边界信息的 DOMRectReadOnly. 边界的计算方式与 Element.getBoundingClientRect() 相同。
  • intersectionRatio:返回intersectionRect 与 boundingClientRect 的比例值。
  • intersectionRect:返回一个 DOMRectReadOnly 用来描述根和目标元素的相交区域。
  • isIntersecting:返回一个布尔值,如果目标元素与交叉区域观察者对象 (intersection observer) 的根相交,则返回 true .如果返回 true, 则 IntersectionObserverEntry 描述了变换到交叉时的状态; 如果返回 false, 那么可以由此判断,变换是从交叉状态到非交叉状态。
  • rootBounds:返回一个 DOMRectReadOnly 用来描述交叉区域观察者 (intersection observer) 中的根。
  • target:与根出现相交区域改变的元素 (Element)。
  • time:返回一个记录从 IntersectionObserver 的时间原点到交叉被触发的时间的时间戳。

1.1.3 IntersectionObserverEntry

IntersectionObserverEntry (观察者实例)包含以下只读属性:root, rootMargin, thresholds,以及如下方法:

  • .disconnect():停止监听工作
  • .observer(targetElement): 开始监听一个元素
  • .takeRecords():返回所有被观察对象的列表
  • .unobserve(target):停止监听特定目标

2. Intersection Observer 的典型应用

2.1 图片的懒加载

这可能是最常见的应用场景之一了,具体流程可以是:通过将页面视口创建成观察者对象 ➡️ 绑定页面中的所有图片占位符 ➡️ 当观察者回调被触发之后 ➡️ 遍历匹配的对象 ➡️ 通过isIntersecting判断将占位符替换成实际图片。这里就不过多赘述了。

2.2 滚动到页面底部的自动加载功能

我们经常见到的滚动到页面底部自动加载动态的功能,也是可以通过 Intersection Observer 巧妙的实现的:在页面底部放置一个类似加载更多的被观察元素,当这个元素出现在视野内之后,调用API去获取更多的数据填充进来,以此往复。

写了个简单的Demo:

Demo传送门

3. 福利

在查阅相关材料的同时找到一些比较好的Demo或者文档,分享给大家:

3.1. Intersection Observer API 原理示例

在codepen中找到一个很好的示例,可以通过交互的方式完全理解Intersection Observer API的相关参数

链接传送门

Demo 原作者 https://codepen.io/michellebarker

3.2. 如何在React中使用 - 《Lazy Loading Images in React》

如果你使用的是React,这里有一个很好的通过React使用Intersection Observer API实现懒加载的文章,关键字 useEffect, useRef

链接传送门