先说三个情景,第一是懒加载图片,会先加载你可视化区域内的图片,当滚动条往下滑动的时候才会加载下面的;第二是瀑布流布局,随着滚动条往下滑动,会一直加载图片出来是一个无限滚动的效果;第三个是播放一个视频,当滚动条往下滑动的时候,视频显示不全,此时视频是暂停,等往上滑动,视频出现完整的时候才会继续播放。这三个效果它们都跟滚动条有关系,按照常理我们要去检测滚动事件,计算滚动条的位置,然后做相应的处理,但是这样不仅效率低,而且滚动事件会不停的触发,并且实现起来比较麻烦。但是Intersection Observer API
可以很好的帮我们处理这个问题。
Intersection
表示交叉,指两个元素有个交叉,Observer
表示观察,简单来说就是观察两个元素有没有交叉。这个跟我们要实现的效果有啥关系呢?我们可以去试一下:
1.懒加载图片
先搭建一下场景:放一千张图片,其中src
下的是静态资源,是默认图片,默认图片可以加入缓存所以加载起来很快,真实图片放到自定义属性data-src
,目前显示的是默认图片,当图片进入到可视化区域的时候加载真实图片。
html
<template>
<div class="intersection-observer-api">
<div class="header">
这里是IntersectionObserverAPI组件的内容。
</div>
<div class="container card-border">
<div class="item" v-for="item in 1000" :key="item">
<img src="./imgs/1.jpg" alt="" data-src="https://picsum.photos/400/600?r=1" />
</div>
</div>
</div>
</template>

整体思路是,我们去观察这个图片,如果这个图片跟我们的可视化区域有没有交叉,有交叉说明这个图片出现在我们的可视化区域里面,这个时候我们就把这个真实图片显示出来。

首先我们创建一个ob
,就是观察者IntersectionObserver
,这里面要传递一个回调函数,这个回调函数就是交叉的时候会运行,比如从交叉变到不交叉,或者从不交叉变到交叉,这个时候就会运行。与此同时,在调用这个回调函数的时候,还要传递一个配置,这个配置也很简单,一个是root
,一个是rootMargin
,一个是threshold
。
-
root: 表示要观察的元素跟谁交叉,比如我们观察那个图片要跟谁交叉,一般就是它的父元素或者父元素的父元素等等(不能是子元素),如果是null表示跟可视化区域交叉。
-
rootMargin:对交叉范围进行扩张,比如下面这个图就属于图片和可视化区域进行了交叉。

而rootMargin
可以把这个范围扩大,比如值为10px
,就会把交叉范围往外扩大10px
,如下图就算进行了交叉。当然可以收缩,值写为负值就行。

- threshold :表示交叉的阈值,填写的范围在
0~1
之间,比如说0.5
,就是说这个图片必须要有一半和可视化区域交叉才会触发回调函数。写0
的话只要是碰上了就会执行。

js
const ob = new IntersectionObserver(() => {
console.log('进入视口');
}, {
root: null, // 默认值为null,表示以浏览器视口作为容器
rootMargin: '0px', // 默认值为'0px'
threshold: 0.1 // 默认值为0,表示当目标元素有10%的区域进入视口时触发回调
});
这样的话还差一个东西,就是要观察的目标,我们这里要观察的目标就是所有带有自定义属性data-src
的图片。我们要先获取所有要观察的目标,然后去观察每个目标,怎么观察呢,就是给每个目标调用前面定义的ob
对象的observe
方法。
js
// 获取所有要观察的图片元素
const imgs = document.querySelectorAll('img[data-src]');
// 观察每个图片元素 怎么观察就是调用ob对象的observe方法
imgs.forEach((img) => {
ob.observe(img);
});
我们运行一下试试看。

可以看到后台已经有打印了,说明ob
的回调方法已经调用了,但是为什么只调用了一次呢,明明有很多图片都交叉了,其实是这样的,它把所有的交叉结果会通过回调函数的参数entries
传给你,entries
是一个数组,我们去打印一下这个数组。
js
const ob = new IntersectionObserver((entries) => {
console.log(entries);
}, {
root: null, // 默认值为null,表示以浏览器视口作为容器
rootMargin: '0px', // 默认值为'0px'
threshold: 0.1 // 默认值为0,表示当目标元素有10%的区域进入视口时触发回调
});

可以看到这个数组有1000项,这是因为我们有一千个目标。但是我们交叉的图片只有20个,我们去看一下数组前20个元素,每一个元素都是一个Entry
对象,每一个Entry
对象里面就记录了这个目标跟我们的可视化区域是怎么交叉的,要注意两个属性,一个是target
,表示目前观察的目标,第二个属性是isIntersecting
,它表示目标是否和可视化区域进行了交叉。

可以看到前20个目标的isIntersecting
为true
,表示目标和可视化区域已经交叉了。
所以我们就在回调函数里面去循环这个entries
参数,判断每一项的isIntersecting
,就可以知道这个目标跟我们的可视化区域到底有没有交叉。如果目标交叉了我们就可以直接通过Entry
对象的target
属性获取到目标,然后给目标的src
属性重新赋值为data-src
的值。然后调用ob
对象的unobserve
方法停止观察该目标。
js
const ob = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) { // 判断元素是否进入视口
const img = entry.target; // 获取目标元素
const dataSrc = img.getAttribute('data-src'); // 获取图片的真实路径
if (dataSrc) {
img.src = dataSrc; // 设置图片的src属性,开始加载图片
img.removeAttribute('data-src'); // 移除data-src属性,避免重复加载
}
ob.unobserve(img); // 停止观察该元素,提高效率
}
});
}, {
root: null, // 默认值为null,表示以浏览器视口作为容器
rootMargin: '0px', // 默认值为'0px'
threshold: 0.1 // 默认值为0,表示当目标元素有10%的区域进入视口时触发回调
// 获取所有要观察的图片元素
const imgs = document.querySelectorAll('img[data-src]');
// 观察每个图片元素 怎么观察就是调用ob对象的observe方法
imgs.forEach((img) => {
ob.observe(img);
});
效果如下,因为不是很明显,所以我给threshold
值设置为0.5
了,这样能看的清楚点。
