Intersection Observer API

先说三个情景,第一是懒加载图片,会先加载你可视化区域内的图片,当滚动条往下滑动的时候才会加载下面的;第二是瀑布流布局,随着滚动条往下滑动,会一直加载图片出来是一个无限滚动的效果;第三个是播放一个视频,当滚动条往下滑动的时候,视频显示不全,此时视频是暂停,等往上滑动,视频出现完整的时候才会继续播放。这三个效果它们都跟滚动条有关系,按照常理我们要去检测滚动事件,计算滚动条的位置,然后做相应的处理,但是这样不仅效率低,而且滚动事件会不停的触发,并且实现起来比较麻烦。但是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个目标的isIntersectingtrue,表示目标和可视化区域已经交叉了。

所以我们就在回调函数里面去循环这个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了,这样能看的清楚点。

待更新

相关推荐
薛定谔的算法3 小时前
《虚拟 DOM 与 Diff 算法:用 1500 字把它讲成“人话”》
前端·react.js·前端框架
Mintopia3 小时前
🚀 Next.js 企业级文件上传方案全解
前端·javascript·全栈
雾岛听风来3 小时前
k9s监控k8s集群工具
前端
用户87261342418514 小时前
封装组件库并上传npm源
前端
Mintopia4 小时前
🌐 Web3.0 时代:AIGC 如何赋能去中心化内容生态?
前端·javascript·aigc
鹏多多4 小时前
前端项目eslint配置选项详细解析
前端·vue.js·react.js
然我4 小时前
面试官:这道 Promise 输出题你都错?别再踩 pending 和状态凝固的坑了!(附超全解析)
前端·javascript·面试
bug_kada4 小时前
让你彻底明白什么是闭包(附常见坑点)
前端·javascript
吴楷鹏4 小时前
TypeScript 为什么要增加一个 satisfies?
前端·typescript