IntersectionObserver

IntersectionObserver

按照惯例,我们先来看看 IntersectionObserver 到底是个什么东西,MDN 上的介绍的是,IntersectionObserver 接口(从属于 Intersection Observer API),提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法,祖先元素与视窗(viewport)被称为根(root

当一个 IntersectionObserver 对象被创建时,其被配置为监听根中一段给定比例的可见区域,一旦 IntersectionObserver 被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值,然而你可以在同一个观察者对象中配置监听多个目标元素

看上去云里雾里的,其实简单的总结一下,所谓的 IntersectionObserver 说的就是一种监听目标元素与其祖先或视窗交叉状态的手段,也就是观察一个元素是否在视窗可见

如上图,交叉了就是说明当前元素在视窗里,当前就是可见的了,但是有一点特别需要注意的就是,必须是子元素跟父(祖先)元素发生交叉,如果你想检查两个非父子关系的交叉,是不可以的

本文所有示例可见 IntersectionObserver

基本概念

先来看看如何使用,使用方式很简单

1
2
3
4
const io = new IntersectionObserver(callback, options)
io.observe(element) // 观察元素
io.unobserve(element) // 停止观察
io.disconnect() // 关闭观察器

下面我们就来详细介绍具体的配置参数

构造函数

1
new IntersectionObserver(callback, options)

其实就是一个简单的构造函数,它会返回一个 IntersectionObserver 实例,接收两个参数

  • callback,是当元素的可见性变化时候的回调函数
  • options,是一些配置项(可选的)

callback

当元素的可见性变化时,就会触发 callback 函数,callback 函数会触发两次,元素进入视窗(开始可见时)和元素离开视窗(开始不可见时)都会触发,它接受一个 entries 参数,返回当前已监听并且发生了交叉的目标集合

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

io.observe(document.body)

运行结果如下($0 表示上一次审查元素的选择的节点)

我们可以看到 callback 函数有个 entries 参数,它是个 IntersectionObserverEntry 对象数组,各个属性如下

属性 解释
boundingClientRect 目标元素的矩形信息
intersectionRatio 相交区域和目标元素的比例值 intersectionRect/boundingClientRect,不可见时小于等于 0
intersectionRect 目标元素和视窗(根)相交的矩形信息,可以称为相交区域
isIntersecting 目标元素当前是否可见值,如果可见则为 true
rootBounds 根元素的矩形信息,没有指定根元素就是当前视窗的矩形信息
target 观察的目标元素
time 返回一个记录从 IntersectionObserver 的时间到交叉被触发的时间的时间戳

如下图所示

options

几个比较常用的参数如下

  • root

用于观察的根元素,默认是浏览器的视口,也可以指定具体元素,指定元素的时候用于观察的元素必须是指定元素的子元素

  • threshold

用来指定交叉比例,决定什么时候触发回调函数,是一个数组,默认是 [0]

1
2
3
4
5
6
7
const options = {
root: null,
threshold: [0, 0.5, 1]
}

var io = new IntersectionObserver(callback, options)
io.observe(document.querySelector('img'))

上面代码,我们指定了交叉比例为 [0, 0.5, 1],当观察元素 img 处于 0%50%100% 时候就会触发回调函数

  • rootMargin

用来扩大或者缩小视窗的的大小,使用 CSS 的定义方法,10px 10px 30px 20px 表示 toprightbottomleft 的值

1
2
3
4
5
const options = {
root: document.querySelector('.box'),
threshold: [0, 0.5, 1],
rootMargin: '30px 100px 20px'
}

为了方便理解,可以参考下图

首先我们先来看看上图当中的蓝线部分,它就是我们定义的 root 元素,我们添加了 rootMargin 属性,将视窗的增大了,虚线就是现在的视窗,所以元素现在也就在视窗里面了,简单来说就是自定义元素进入视窗的距离,由此可见,root 元素只有在 rootMargin 为空的时候才是绝对的视窗

实际应用

在了解完 IntersectionObserver 的相关概念之后,我们来看几个具体的实例,本文所有示例可见 IntersectionObserver

监听元素

先来看一个最简单的示例,主要代码如下

1
2
3
4
5
6
7
8
9
let box = document.querySelector('.box')
let observe = new IntersectionObserver(entries => {
entries.forEach(item => {
let tips = item.isIntersecting ? `进入` : `离开`
console.log(tips)
})
})

observe.observe(box)

结果如下图所示

打开控制台可以发现,每次当元素进入或者离开可视区的时候,控制台当中就会输出对应的字段,我们再来稍微的调整一下,将一个元素替换为多个元素,代码如下

1
2
3
4
let box02 = document.querySelectorAll('.box')
let observe02 = new IntersectionObserver(entries => console.log(`发生交叉行为,目标元素个数为 ${entries.length} 个`))

box02.forEach(item => observe02.observe(item))

结果如下图所示

根据上图可以发现,虽然元素变成了三个,但是每次发生交叉的时候是一起发生的,再来调整一下,同样还是三个元素,但是排列方式不一样,结果如下图所示

可以发现,在这种情况之下每个目标轮流发生交叉,且当前只触发了一个,所以每次返回的集合长度只有一,如果想指定监听父元素,可以使用 root 参数

1
2
3
4
5
6
7
8
9
10
11
let box = document.querySelector('.box')
let observe = new IntersectionObserver(entries => {
entries.forEach(item => {
let tips = item.isIntersecting ? `进入` : `离开`
console.log(tips)
})
}, {
root: document.querySelector('.wrapper')
})

observe.observe(box)

现在我们已经基本了解了 IntersectionObserver 的一些基本用法,下面我们就来看一些实际场景当中的应用

图片懒加载

以前的做法是监听浏览器滚动,然后遍历拿到每个图片的空间信息,然后判断一些位置信息从而进行图片加载,而现在只需要交给交叉观察者去做,页面布局如下

1
2
3
4
5
6
7
8
<!-- 多个 img 元素 -->
<img src="https://via.placeholder.com/200x130" data-origin="图片真实地址">
<img src="https://via.placeholder.com/200x130" data-origin="图片真实地址">

// ...

<img src="https://via.placeholder.com/200x130" data-origin="图片真实地址">
<img src="https://via.placeholder.com/200x130" data-origin="图片真实地址">

然后针对图片列表进行监听

1
2
3
4
5
6
7
8
9
10
11
12
let images = document.querySelectorAll('img')
let observe = new IntersectionObserver(entries => {
entries.forEach(item => {
// 如果目标元素当前可见,则加载图片资源
if (item.isIntersecting) {
item.target.src = item.target.dataset.origin
observe.unobserve(item.target)
}
})
})

images.forEach(item => observe.observe(item))

效果如下,只有当对应图片进入可视区以后才会进行加载

也可以通过设置 rootMargin,来自定义图片进入视窗多少距离以后在进行加载,另外,使用该方法如果是横向滚动懒加载一样是可以使用的

传统的懒加载只是监听全局滚动条的滚动,因为它并不是判断目标是否出现在视窗,所以横向的图片会一起加载,即使你没有向左滑动,所以这也是交叉观察者的一大优点

触底

使用交叉观察者可以让我们轻松实现滚动到底部自定加载的功能,我们首先在列表底部放一个参照元素,然后使用交叉观察者去监听,布局如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div class="box">
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
<li>7</li>
<li>8</li>
</ul>
<!-- 参照元素 -->
<div class="footer"></div>
</div>

classfooter 的元素就作为我们的参照元素,下面就可以对其进行监听

1
2
3
4
5
6
new IntersectionObserver(entries => {
// 这里使用 forEach 也可,但是我们的示例只有一个,直接使用也行
if (entries[0].isIntersecting) {
console.log(`滚动到了底部!`)
}
}).observe(document.querySelector('.footer'))

结果如下

动画展示

一个比较常见的效果,即当某个元素或者列表出现的时候就给该元素或者列表加个动画,比如渐变、偏移等,下面我们就来实现看看,首先是页面布局,跟上面的示例其实差不多,但是这里采用两列,主要 CSS 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
li.show {
animation: left 1s ease;
}

li.show:nth-child(2n) {
animation: right 1s ease;
}

@keyframes left {
from {
opacity: 0;
transform: translate(-40px, 40px);
}

to {
opacity: 1;
}
}

@keyframes right {
from {
opacity: 0;
transform: translate(40px, 40px);
}

to {
opacity: 1;
}
}

最终效果如下

兼容性

IntersectionObserver 的兼容性可见下图

从上图可以发现,IE 是不兼容的,但是官方提供了 polyfill,详细可见 IntersectionObserver

参考

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×