探索 Intersection Observer API:提升网页性能的新途径
Intersection Observer API
在介绍 Intersection Observer API 之前,首先要先了解另外三个知识点,分别是 IntersectionObserver() 构造器
、IntersectionObserver 对象
、IntersectionObserverEntry 对象
它们的关系相对来说比较复杂,我们可以先通过一张图来大致了解一下它们的关系,及一些属性与方法等
1. Intersection Observer API 基本介绍
Intersection Observer API 提供了一种异步检测目标元素与祖先元素或视口相交情况变化的方法,其祖先元素或视口被称为根(root)
Intersection Observer API 出现的原因:
- 在开发中过程中,我们可能常常需要判断某个元素是否进入了视口(viewport),即用户能不能够看到它
- 在面对判断一个元素是否进入视口或某一个区域时,以前我们通常会使用
Element.getBoundingClientRect()
方法获取对应元素的位置信息等,且还需要用到事件监听等 - 然而事件监听和调用
Element.getBoundingClientRect()
等,都是运行在主线程的,因此如果平凡处理滚动条的监听函数等操作,就会造成比较大的性能问题,并且这种监测方法使用其它比较繁琐 - 因此官方就提出了
Intersection Observer API
该 API 是异步的,它不会随着滚动条的滚动而触发,而是通过 requestIdleCallback() 来实现,只有在浏览器空闲下来时,才会触发对应的观察器的执行(所以就不会象通过监听滚动条的方式,频繁触发从而导致性能的问题)
且在该 API 中的 IntersectionObserverEntry 实例中就包含了对应目标元素与根元素相应的位置信息等(即如果想要获取元素的位置信息时,我们不需要在手动通过调用 Element.getBoundingClientRect() 方法来获取)
Intersection Observer 基本概念:
- 目标(target) - 我们所要监听的目标元素
- 根元素(root) - 用于判断目标元素是否符合条件的元素(默认为顶级文档的视口)
- 交叉比例(intersection ratio) - 目标元素与根元素的交集相对于目标元素百分比的表示(取值: 0.0-1.0)
- 阈值(threshold) - 回调函数的触发条件
- 回调函数(callback) - IntersectionObserver API 配置的回调函数,会在设定的条件下进行触发
2. IntersectionObserver() 构造器
IntersectionObserver() 构造器用于创建一个 IntersectionObserver 对象
-
const intersectionObserver = new IntersectionObserver(callback[, options]);
该对象会根据对应设定的阈值来监听目标元素,通过调用自身的 observer() 方法开启对某个目标元素的监听
IntersectionObserver() 构造器的参数:
-
callback: - 当交叉比超过指定阈值触发回调函数,此函数可接受两个参数(entries / observer)
entries:
由IntersectionObserverEntry
对象组成的数组 但每个被触发的阈值,都或多或少与指定阈值有偏差- observer: 返回被调用的
IntersectionObserver
实例(等价于IntersectionObserver
构造器返回的实例对象)
-
options(可选): - 用于配置回调函数触发的条件,其参数下还有三个子参数
root:
指定根元素 -用于检查目标的可见性(默认为浏览器视口) → tip: 如果指定为 null 也为浏览器视口 & 必须是目标元素的父级元素
****rootMargin:
根元素的扩缩边距 -其传值形式与 CSS 中 margin 一样,用于控制根元素每一边的扩缩(单位为 px 或 %),从而控制计算根元素和目标元素的交集的区域范围,默认值为 0
threshold:
阈值,回调函数触发的条件(取值范围: 0.0-1.0、默认值: 0.0)-tip: 如果传入数值时,只会触发一次 / 如果传入一个包含多个阈值的数组时,会在达到每个阈值时都会触发对应的回调函数
-
+ intersectionObserver 对象的基本创建 const intersectionObserver = new IntersectionObserver((entries, observer) => { // -- 参数 1: callbackconsole.log(intersectionObserver === observer) // -- true},{ // -- 参数 2: optionsroot: document.querySelector(".infinite-list-container"), // -- 指定根元素rootMargin: "0px", // -- 跟元素的扩缩边距threshold: 0, // -- 阈值: 回调函数触发的条件} )
3. IntersectionObserver 对象
三个属性: - 该对像的三个属性与IntersectionObserver()
构造器的 options 参数类似,并且这三个属性都只能读取操作,不能进行更改
属性 | 说明 | 默认值 |
---|---|---|
root | 指定根元素。如果传值为 null,则为顶级文档的视窗 | 顶级文档的视口(一般为 html) |
rootMargin | 根元素的扩缩边距值 | “0px 0px 0px 0px” |
thresholds | 一个包含阈值的数组,并按升序排列,列表中的每个阈值都是监听对象的交叉区域与边界区域的比率 | 0 |
四个方法:
方法 | 说明 |
---|---|
observe(target) | 开始监听指定目标元素 |
unobserve(target) | 停止监听指定的目标元素 |
takeRecords() | 返回所有观察目标的 IntersectionObserverEntry 对象数组 |
disconnect() | 使 IntersectionObserver 对象停止全部监听工作 |
4. IntersectionObserverEntry 对象
IntersectionObserverEntry 接口(从属于 Intersection Observer API)描述了目标元素与其根元素容器在某一特定过渡时刻的交叉状态
IntersectionObserverEntry 对像数组作为 entries 参数传递给 IntersectionObserver 对像的回调函数中; 此外,这对象数组只能通过调用 IntersectionObserver.takeRecords() 来获取
该对象的主要属性如下(只读)
属性 | 说明 |
---|---|
target | 返回目标元素,表示目前该对象正监听的元素 |
isIntersecting | 返回一个布尔值,目标元素刚出现在根元素可视区时返回 true;目标元素从根元素可视区消失返回 false;以上两种情况都会触发 callback 函数 |
boundingClientRect | 返回目标元素的矩形区域的信息,返回结果与 element.getBoundingClientRect() 相同 |
rootBounds | 返回根元素的矩形区域的信息,getBoundingClientRect() 方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回 null |
intersectionRect | 返回目标元素与视口(或根元素)的交叉区域的信息 |
intersectionRatio | 返回目标元素的可见比例,即intersectionRect 占boundingClientRect 的比例,完全可见时为 1,完全不可见时小于等于 0 |
time | 返回一个记录从IntersectionObserver 的时间原点到交叉被触发的时间的时间戳 |
5. Intersection Observer API 的简单案列
<div class="list"><div class="item">item-1</div><div class="item">item-2</div><div class="item">item-3</div><div class="item">item-4</div><div class="item">item-5</div><div class="item">item-6</div><div class="item">item-7</div><div class="item">item-8</div><div class="item">item-9</div><div class="item">item-10</div>
</div>
/* 为了可以滚动,将每一个 item 的高度设高一点 */
.item {height: 200px;background-color: orange;color: white;line-height: 200px;text-align: center;border-radius: 6px;
}
const intersectionObserver = new IntersectionObserver( // -- 1. 创建 IntersectionObserver 实例对象(entries, observer) => {entries.forEach(ioe => { // -- 2. 遍历 entries,获取每一个 IntersectionObserverEntry 对象,并对齐进行相应的处理...const target = ioe.target // -- 3. 通过 IntersectionObserverEntry 实例对象中的 target,获取当前触发回调的目标元素// -- 4. 通过 IntersectionObserverEntry 实例对象中的 isIntersecting 判断当前是否符合进入视口的条件,不符合直接返回不做其它操作if (!ioe.isIntersecting) return console.log(target.textContent + ": 不符合条件,元素在视口区域外")console.log(target.textContent + ": 元素已进入可视视口")})},{// -- options 按默认}
)// -- 5. 获取 .list 中的 所有 .item 元素,并遍历监听所有 .item 元素
const itemEls = [...document.querySelectorAll(".item")] // -- 因为 document.querySelectorAll 返回的是一个伪数组对象,这里先将其转化成一个真正的数组对象,方法下面进行遍历
itemEls.forEach(itemEl => {intersectionObserver.observe(itemEl) // -- 通过 IntersectionObserver 实例对象中的 observe,来监听对每个 .item 元素进行监听(是否进入视口)
})
6. Intersection Observer API 的常见应用场景
处理某个元素是否可见(如 ↓):
- 图片懒加载 - 当图片滚动到可见时才进行加载
- 内容无限滚动 - 当用户滚动到接近底部时直接加载更多,而无需翻页,给用户一种网页可以无限滚动的错(数据量比较大的长列表,为了优化 DOM 的渲染性能,最好采用虚拟列表的方式进行实现)
- 虚拟列表 - 处理列表元素宽高并非固定的虚拟列表方案(性能优化)
两个元素是否相交(如 ↓):
- 检测广告的曝光情况 - 为了计算广告收益,需要知道广告元素的曝光情况
- 在用户看见某个区域时执行任务或播放动画