Skip to content

这个东西叫做交叉观察器,用于观察一个元素和其祖先元素(或者是视口)是否交叉。

也就是说交叉观察器其实可以干两件事,第一件事是判断一个元素是否和其某个祖先元素交叉,第二件事是判断一个元素是否和视口交叉。

那么什么是交叉呢?所谓的交叉,就是在视觉上两个元素发生重叠,就是交叉。当然,所谓的元素和视口的交叉,实际上就是元素可以被看见,就算元素和视口交叉。简单点说呢,一个元素可能在其祖先元素内部,也可能在其祖先元素外部,这个祖先元素就可以去观察这个目标元素,如果这个目标元素从我祖先元素的内部跑到了外部,那这就是交叉情况发生了变化,同样,如果目标元素从我祖先元素的外部跑到了内部,也是发生变化。

当然,一定是某个祖先元素去观察他里面的目标元素和他的位置关系。

用法其实非常简单,首先我们是祖先元素去观察他里面的目标元素的交叉情况,所以这个交叉观察器应该是加在祖先元素上的。同时,应该让这个交叉观察器去观察这个元素内部的某个目标元素,我们可以写出如下代码:

html
<!DOCTYPE html>
<html lang="cmn-hans">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
        body {
            margin: 0;
        }
        .box {
            width: 100px;
            height: 100px;
            background-color: aqua;
            position: absolute;
            left: 0;
            top: 0;
        }
        .box2 {
            width: 50px;
            height: 50px;
            background-color: red;
            position: absolute;
        }
    </style>
</head>
<body>
    <div class="box">
        <div class="box2"></div>
    </div>
</body>
<script>
    const box = document.querySelector(".box");
    const io = new IntersectionObserver(function () {
        console.log("交叉了");
    }, {
        root: box
    });
    io.observe(document.querySelector(".box2"));
</script>
</html>

当我们 new IntersectionObserver() 的时候,就创建了一个交叉观察器,这个构造函数需要传入两个参数,第一个是回调函数,第二个是配置对象,其中第一个回调函数就是当交叉状况发生变化的时候要执行的回调,而第二个配置对象其实一共有三个配置,这里一个一个介绍。

  • root:这个属性用于绑定一个祖先元素,如果不配置 root,那么就是浏览器视口
  • threshold:取值范围为 0 到 1,用于设置阈值
  • rootMargin:这个属性可以在逻辑上扩大祖先元素的逻辑大小,默认值为"0px 0px 0px 0px"

这里需要介绍一下后面两个配置,首先是 threshold,这个是用于设置阈值的,什么是阈值呢,很简单,就是什么情况下算交叉发生变化,默认值就是 0,如果设置为 0,表示只要目标元素和自己有一像素的重叠,都算交叉,如果设置为 1,表示只有当目标元素完全处于自己内部的情况下,才算交叉。而 rootMargin 也很好理解,这个就相当于给祖先元素虚拟往外扩了一圈,比如祖先元素原来是 50*50,但是只要设置了 rootMargin,那么他虽然视觉上还是 50*50,但是判断交叉的边界已经变了。

![Pasted image 20240511162248](/notes-assets/05.WEBAPI/assets/Pasted image 20240511162248.png)

![Pasted image 20240511162438](/notes-assets/05.WEBAPI/assets/Pasted image 20240511162438.png)

以上介绍完了 IntersectionObserver 的配置对象,接下来我们就有了一个 IntersectionObserver 对象,也就是交叉观察器对象,这个对象上有一个方法 observe 方法,该方法传入一个该元素的后代元素作为目标元素,此时当祖先元素(也就是绑定交叉观察器的元素)和后代元素(也就是观察的元素)的交叉关系发生变化的时候,就会自动调用第一个回调函数,也就是输出“交叉了”三个字。

![Pasted image 20240511163143](/notes-assets/05.WEBAPI/assets/Pasted image 20240511163143.png)

读者进入页面会发现,我这里啥也没干,为什么会输出呢,这其实是引入 CSS 渲染时候出现的问题,这无关紧要。

![recording 11](../04.CSS3/assets/recording 11.gif)

我们看这个例子,注意控制台,当 box 2 的位置移出 box 的时候,被 IntersectionObserver 监听到了,并触发了回调函数,同样,当 box 2 的移入 box 的时候,也被 IntersectionObserver 监听到了,同样触发了回调函数。接下来的问题是,移入和移出显然是两个不同的情况,但均触发了同一个回调函数,为了针对这两个不同情况进行处理,我们必须在回调函数中区分这两种情况。

在回调函数内部会给我们传入两个参数,分别是 entries 和 observe,第一个参数为一个数组,就是被观测对象的集合,我们可以通过被观测对象上的一些属性来判断当前是什么情况,比如被观测对象上有一个 isIntersecting 属性,这个属性就可以用来判断当前是否处于交叉状态。第二个参数就是交叉观察器本身。

我们对代码进行简单修改

js
const box = document.querySelector(".box");
const io = new IntersectionObserver(function (entries, observe) {
	if (entries[0].isIntersecting) {//使用isIntersecting来判断
		console.log("进入视口");
	} else {
		console.log("离开视口");
	}
}, {
	root: box
});
io.observe(document.querySelector(".box2"));

![recording 12](../04.CSS3/assets/recording 12.gif)

此时我们就可以区分开这两种情况,当然,被监听对象上也有一些属性和方法,可参考:IntersectionObserverEntry - Web API | MDN

既然可以观察,那一定也可以取消观察,在交叉观察器上有一个 unobserve() 方法,调用该方法并传入一个元素,就可以取消对该元素进行观察。

在观察器上还有两个属性,分别是 disconnect(),该方法可以终止所有观察,还有 takeRecords(),该方法输出的其实就是回调函数内的 entries。

接下来我们就可以使用这里的知识做一个图片懒加载的效果

html
<!DOCTYPE html>
<html lang="cmn-hans">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
        body {
            margin: 0;
        }

        .container {
            display: grid;
            grid-template: 200px/repeat(5, 150px);
            gap: 30px;
            justify-content: center;
            grid-auto-rows: 200px;
        }

        .container>div {
            background-image: url("https://picsum.photos/id/1/150/200");
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="img" data-src="https://picsum.photos/150/200?random=1"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=2"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=3"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=4"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=5"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=6"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=7"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=8"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=9"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=10"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=11"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=12"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=13"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=14"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=15"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=16"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=17"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=18"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=19"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=20"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=21"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=22"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=23"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=24"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=25"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=26"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=27"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=28"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=29"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=30"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=31"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=32"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=33"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=34"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=35"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=36"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=37"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=38"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=39"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=40"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=41"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=42"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=43"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=44"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=45"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=46"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=47"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=48"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=49"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=50"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=51"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=52"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=53"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=54"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=55"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=56"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=57"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=58"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=59"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=60"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=61"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=62"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=63"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=64"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=65"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=66"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=67"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=68"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=69"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=70"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=71"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=72"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=73"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=74"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=75"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=76"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=77"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=78"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=79"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=80"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=81"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=82"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=83"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=84"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=85"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=86"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=87"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=88"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=89"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=90"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=91"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=92"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=93"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=94"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=95"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=96"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=97"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=98"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=99"></div>
        <div class="img" data-src="https://picsum.photos/150/200?random=100"></div>
    </div>
</body>
<script>
    /**
    * 创建并初始化一个IntersectionObserver实例,用于观察图片元素是否进入可视区域,并在进入时加载背景图片。
    * @param {Object} options 配置参数,这里设置了rootMargin为"20px",即根元素的边距为20px。
    */
    const observe = new IntersectionObserver(function (entries, observe) {
        // 对每个被观察的入口进行处理
        entries.forEach(({ target, isIntersecting }) => {
            // 如果元素进入可视区域
            if (isIntersecting) {
                // 获取data-src属性值作为图片地址,并设置为元素的背景图片
                const src = target.dataset.src;
                target.style.backgroundImage = `url(${src})`;
                observe.unobserve(target);//取消观察
            }
        });
    }, {
        rootMargin: "20px" // 设置根元素的边距,影响交叉区域的计算
    });

    // 获取所有需要被观察的图片元素
    const imgList = document.querySelectorAll(".img");

    // 对每个图片元素进行观察
    imgList.forEach(item => {
        observe.observe(item);
    });
</script>
</html>

以笔记为镜,可以知得失