使用Intersection Observer API 检测元素是否出现在可视窗口
使用Intersection Observer API 检测元素是否出现在可视窗口
API解读:
Intersection Observer API提供了一种异步检测目标元素与祖先元素或视口(可统称为根元素)相交情况变化的方法。
注意点:因为该 API 是异步的,它不会随着目标元素的滚动同步触发,而IntersectionObserver API是通过requestIdleCallback()实现,即只有浏览器空闲下来,才会执行观察器。
Intersection observer 的重要概念
Intersection observer API 有以下五个重要的概念:
- 目标(target)元素 — 我们要监听的元素
- 根(root)元素 — 帮助我们判断目标元素是否符合条件的元素
- 以下两种情况根元素会默认为顶级文档的视口(一般为 html)。
- 目标元素不是可滚动元素的后代且不传值时
- 指定根元素为 null
- 交叉比(intersection ratio)—目标元素与根根的交集相对于目标元素百分比的表示(取值范围 0.0-1.0)。
- 阈值(threshold) — 回调函数触发的条件。
- 回调函数(callback) — 为该 API 配置的函数,会在设定的条件下触发。
用法
是以new
的形式声明一个对象,接收两个参数callback
和options
1 | const io = new IntersectionObserver(callback [,options]) |
callback
callback是添加监听后,当监听目标发生滚动变化时触发的回调函数。接收一个参数entries,即IntersectionObserverEntry实例。描述了目标元素与root的交叉状态。具体参数如下:
属性 | 说明 |
---|---|
boundingClientRect | 返回包含目标元素的边界信息,返回结果与element.getBoundingClientRect() 相同 |
intersectionRatio | 返回目标元素出现在可视区的比例 |
intersectionRect | 用来描述root和目标元素的相交区域 |
isIntersecting | 返回一个布尔值,下列两种操作均会触发callback:1. 如果目标元素出现在root可视区,返回true。2. 如果从root可视区消失,返回false |
rootBounds | 用来描述交叉区域观察者(intersection observer)中的根. |
target | 目标元素:与根出现相交区域改变的元素 (Element) |
time | 返回一个记录从 IntersectionObserver 的时间原点到交叉被触发的时间的时间戳 |
表格中加粗的两个属性是比较常用的判断条件:isIntersecting(是否出现在可视区)和intersectionRatio(出现在可视区的比例)
options
options是一个对象,用来配置参数,也可以不填。共有三个属性,具体如下:
属性 | 说明 |
---|---|
root | 所监听对象的具体祖先元素。如果未传入值或值为null,则默认使用顶级文档的视窗(一般为html)。 |
rootMargin | 计算交叉时添加到根(root)边界盒bounding box的矩形偏移量, 可以有效的缩小或扩大根的判定范围从而满足计算需要。所有的偏移量均可用像素(px)或百分比(%)来表达, 默认值为”0px 0px 0px 0px”。 |
threshold | 一个包含阈值的列表, 按升序排列, 列表中的每个阈值都是监听对象的交叉区域与边界区域的比率。当监听对象的任何阈值被越过时,都会触发callback。默认值为0。 |
方法
介绍了这么多配置项及参数,差点忘了最重要的,IntersectionObserver有哪些方法? 如果要监听某些元素,则必须要对该元素执行一下observe
方法 | 说明 |
---|---|
observe() | 开始监听一个目标元素 |
unobserve() | 停止监听特定目标元素 |
takeRecords() | 返回所有观察目标的IntersectionObserverEntry对象数组 |
disconnect() | 使IntersectionObserver对象停止全部监听工作 |
实际应用
- 图片懒加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14const imgList = [...document.querySelectorAll('img')]
var io = new IntersectionObserver((entries) =>{
entries.forEach(item => {
// isIntersecting是一个Boolean值,判断目标元素当前是否可见
if (item.isIntersecting) {
item.target.src = item.target.dataset.src
// 图片加载后即停止监听该元素
io.unobserve(item.target)
}
})
})
// observe遍历监听所有img节点
imgList.forEach(img => io.observe(img)) - 埋点曝光
假如有个需求,对一个页面中的特定元素,只有在其完全显示在可视区内时进行埋点曝光。
1 | const boxList = [...document.querySelectorAll('li')] |
- demo:
大家可以在自己电脑运行一下下面的代码,会有更深的理解。
1 | <!DOCTYPE html> |
关于如何判断元素是否在可视区域内的其他方法
第一种方法:offsetTop、scrollTop
公式: el.offsetTop - document.documentElement.scrollTop <= viewPortHeight
1 | function isInViewPortOfOne (el) { |
第二种方法:getBoundingClientRect
- 返回值是一个 DOMRect 对象,拥有 left, top, right, bottom, x, y, width, 和 height 属性
公式: el.getBoundingClientReact().top <= viewPortHeight
其实, el.offsetTop - document.documentElement.scrollTop = el.getBoundingClientRect().top, 利用这点,我们可以用下面代码代替方法一
1 | function isInViewPortOfTwo (el) { |