深入理解 Intersection Observer

148 阅读2分钟

交叉观察器 API(Intersection Observer API)提供了一种异步检测目标元素与祖先元素或顶级文档的视口相交情况变化的方法。

详细介绍参见mdn链接

Intersectio和其他Observer类似,主要options不同

options有以下几个参数

  1. root
  • 不填的话碰撞区域参照浏览器视口
  • 填写的话碰撞区域参照这个节点的contentbox,如果这个节点滚动的话,参照滚动区域
  1. rootMargin
  • 调整碰撞区域的大小
  1. threshold
  • 碰撞触发回调的阈值

Intersection Observer的优点:

  1. 异步检测,判断是否触发回调的逻辑不在主线程,不像scroll事件一样每滚动一下就会回调一次,而是通过满足threshold阈值才会回调,比如threshold设置为0.5,那么只有在进入/退出满足0.5个子元素面积的时候才会触发回调,能减少主线程的负载
  2. 碰撞逻辑清晰,如果是scroll来检测碰撞,需要调用getBoundingClientRect计算两个元素坐标是否相交,判断逻辑复杂,而Intersection则把这部分逻辑交给浏览器来处理。

需要注意几个点:

  1. Intersection Observer 并不是用来检测两个兄弟元素相交情况,而是父子元素。简单来说,Intersection Observer 就是划定一个碰撞区域,然后观察绑定元素在这个区域内的碰撞情况,并不是用来判定两个元素相交的,如果要判断兄弟元素相交,还得用getBoundingClientRect
  2. Intersection Observer 并不关心绑定元素被其他元素给遮挡,只关心它在指定区域内的相交情况,如果不是参考视口的话,还要考虑图层关系
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="content" style="height: 500px; overflow: auto;">
    <div id="child" style="height: 1000px; background-color: aqua;">
      <button id="button">red top -10px</button>
      <div id="box" style="width: 100px; height: 100px; background-color: red; position: absolute;"></div>
      <div style="width: 50px; height: 50px; background-color: yellow;z-index: 1; position: relative;"></div>
    </div>
  </div>
  <script>

    let options = {
      // root: document.querySelector("#content"),
      rootMargin: "0px",
      threshold: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1],
    };

    let observer = new IntersectionObserver(callback, options);

    function callback (entries, observer) {
      entries.forEach((entry) => {
        console.log("intersectionRatio: ", entry.intersectionRatio)
      })
    }
    observer.observe(document.querySelector("#box"))
    document.querySelector("#button").addEventListener('click', function() {
      document.querySelector("#box").style.top = '-10px';
    })
  </script>
</body>
</html>

截屏2024-02-01 15.33.27.png

这个demo观测红色方块的碰撞,碰撞区域是浏览器视口

可以看出黄色,红色,蓝色方块都不在同一图层

截屏2024-02-01 15.35.35.png

由于初始化的时候红的方块就完全暴露在视口中,所以是完全相交,没有考虑黄色box的遮挡

录屏2024-02-01 15.47.53.gif

滚动时,红色box位置相对视口没有发生变化,不触发回调

录屏2024-02-01 15.50.39.gif 点击按钮后,红色box超出了视口,触发了回调

此时如果把root改为centent

let options = {
      root: document.querySelector("#content"),
      rootMargin: "0px",
      threshold: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1],
    };

截屏2024-02-01 15.54.37.png

由于红色box和content不在同一图层,它们两个没有相交,此时点击按钮也不会触发回调