那些被忽略但很好用的 Web API / IntersectionObserver

当你进入我的眼帘,我们的命运就有了交集~

看到 Observer,应该就知道今天要介绍的又是「观察者」系列的 API 了,而且这次的观察者可能比前面的 MutationObserver 和 ResizeObserver 还要实用。只要有了它,Scroll Animation 就只是一块小蛋糕了。


IntersectionObserver

IntersectionObserver 帮我们观察的是元素的「相交(intersect)」变动,也就是元素与指定可视窗口的「相交与否」发生变动时触发。

简单来说就是页面元素因卷动而进入到可视范围中,或是离开了可视范围时,IntersectionObserver 就会执行指定任务,所以我们可以利用它来侦测「某个元素是不是进入视窗中」了,而且还可以调整许多细微的侦测设定,相当强大。

# Window.IntersectionObserver

和其他「观察者」一样,IntersectionObserver 为一个建构函示,需要使用 new 关键字来创建实体,并且需要传入 Callback Function 作为参数,该 Callback 会获得一个存放 IntersectionObserverEntry 的阵列以及「观察者(observer)」自身实体,

  • IntersectionObserverEntry: 其中会有一些关於观测元素与可视范围交互的相关资讯,後面会详细介绍。
  • IntersectionObserver: 呼叫 Callback 的 IntersectionObserver 实体,即为该 Function 的 this
const observer = new IntersectionObserver((entries, owner) => {
  console.log(owner); // IntersectionObserver 实体
  entries.forEach((entry) => {
    console.log(entry); // IntersectionObserverEntry 物件
  });
});

另外,IntersectionObserver 除了 Callback 之外还有一个可选的 options 参数可以设定:

const callback = function (entries) {
  console.log(entries);
};

const observer = new IntersectionObserver(callback, {
  root: null,
  rootMargin: "0px 0px 0px 0px",
  threshold: 0.0,
});

这个 options 参数须为物件,并接受上面这三个属性:

  • root: 这个属性将决定要以哪个元素的可视窗口作为观察依据,预设为 null,表示以 Viewport 作为判断依据,也可以设定成其他元素。
  • rootMargin: 这个属性决定的是窗口的缩放,设定规则和 CSS 的 margin,可以给定一个值,也可以四边各自设定,正值为外扩,负值为内缩。
  • threshold: 这个属性是设定触发的比例门槛,当目标元素与可视范围的相交范围「经过」了这道门槛,Callback 就会被触发,举例来说:
    • 预设值 0: 当相交范围的比例「开始大於 0%」或「开始小於 0%」 的瞬间会触发。
    • 设定为 1: 当相交范围的比例「开始大於 100%」或「开始小於 100%」 的瞬间会触发。
    • 设定成阵列 [0, 0.5, 1]: 规则如上,但目标元素就会有三个触发时机。

https://ithelp.ithome.com.tw/upload/images/20211007/20125431uFevqekQos.png

 

# IntersectionObserver.observe

老样子,观察者们都需要我们使用 observe method 来指定观察对象:

const observer = new IntersectionObserver((entries) => {
  console.log(entries);
});

const div = document.querySelector("div");
observer.observe(div);

 

# IntersectionObserver.unobserve

若要注销某元素的观察,IntersectionObserver 一样有 unobserve method 可以使用:

const observer = new IntersectionObserver((entries) => {
  console.log(entries);
});

const div = document.querySelector("div");
observer.observe(div);
observer.unobserve(div);

 

# IntersectionObserver.disconnect

当然也可以一次性的注销所有元素的观察,同样要记得,IntersectionObserver 实体并不会消失,只是没有观测中的元素而已,你依然可以再次使用 observe 来注册一个新的观察:

const observer = new IntersectionObserver((entries) => {
  console.log(entries);
});
const box1 = document.querySelector(".box1");
const box2 = document.querySelector(".box2");
observer.observe(box1);
observer.disconnect();
observer.observe(box2);

 

# IntersectionObserverEntry 物件

IntersectionObserver 和之前介绍的 MutationObserver 和 ResizeObserver 不同,它是**「非同步」**触发的,毕竟「相交与否」这件事情是一个瞬间,不会有不断叠加的状态,所以也就不需要考虑连续触发导致的效能问题,也就是说尽管你非常快速的来回卷动,它也不会将事件合并。

而 IntersectionObserverEntry 需要用阵列存放,是因为会有多个观测中的元素同时进入/离开可视范围的可能,这时候每一笔的 IntersectionObserverEntry 便代表一个元素的变动,而其中有许多属性可以提供我们使用,下面就一一向各位介绍:

  • IntersectionObserverEntry.target
    发生进出变动的目标元素(element),每个「观察者」都会提供的资讯。
  • IntersectionObserverEntry.isIntersecting
    isIntersecting 是当中非常实用的属性,用来表示目前元素是否与可是范围(root)相交,也就是目标元素是否进入到可视范围中。
const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      // 目标元素进入 viewport 时执行
    } else {
      // 目标元素离开 viewport 时执行
    }
  });
});
  • IntersectionObserverEntry.intersectionRatio
    这个属性提供的是目前元素与观察窗口的相交比例,会是一个 0~1 的数值,计算方式是 相交面积 / 目标元素面积
  • IntersectionObserverEntry.boundingClientRect
    这个属性会提供目标元素的尺寸、座标资讯,而它就等於拿 target 去执行昨天介绍的 getBoundingClientRect 所得到的结果。
  • IntersectionObserverEntry.rootBounds
    这个拿到的也会是 getBoundingClientRect 资讯,但计算的是可视窗口的尺寸、座标,要记得有 rootMargin 的影响。
  • IntersectionObserverEntry.intersectionRect
    和前面都一样,不过提供的区块范围很特别,是目标元素与可视窗口的「交叠范围」。

https://ithelp.ithome.com.tw/upload/images/20211007/20125431paTIcWDxrI.png

 

# 使用情境

IntersectionObserver 的使用情境很多,可以做「卷动特效」或是「无限卷动」,下面我们就来试试写个无间卷动的功能看看,先看效果:

<ul class="list">
  <li class="item">
    <div class="avatar"></div>
    <div>
      <h3>Name</h3>
      <h5>
        Lorem ipsum dolor sit, amet consectetur adipisicing elit. Rem tenetur odit.rem ipsum dolor sit, amet consectetur
        adipisicing elit. Rem tenetur odit
      </h5>
    </div>
  </li>
</ul>
const ul = document.querySelector("ul");

// 这个 function 会一次新增20笔项目到 ul 中
// 用来模拟获取资料後渲染画面
const getMoreItem = () => {
  const fragment = document.createDocumentFragment();
  for (let i = 0; i <= 20; i++) {
    const item = document.querySelector("li:first-child");
    const newItem = item.cloneNode(true);
    fragment.appendChild(newItem);
  }
  ul.appendChild(fragment);
};

const observer = new IntersectionObserver(
  function (entries, observer) {
    // 每当目标元素进入画面後就新增20笔,并且重置观察的元素
    if (entries[0].isIntersecting) {
      getMoreItem();
      observer.unobserve(entries[0].target);
      observer.observe(document.querySelector("li:nth-last-child(2)"));
    }
  },
  { root: ul } // 观察窗口为 ul 的元素范围
);

getMoreItem();
observer.observe(document.querySelector("li:nth-last-child(2)"));

无限卷动的功能是非常常见的一个功能,平常在滑 FB 或 IG 时,贴文能不段的出现就是使用无线卷动,而使用 IntersectionObserver 就可以轻松做到。

我们实践的概念也非常简单,就是在目标元素进入窗口时去向後端获取资料并渲染在画面上,然後不断的重新观察倒数第二个 li,所以可以看到 Callback 中有执行 unobserve 来注销原本的元素,然後又再次使用 observe 来注册新元素。

 

IntersectionObserver 是不是非常方便呢?昨天我们还在用 getBoundingClientRect 来计算元素是否进入画面,今天已经连计算都不用计算,完全交由「观察者」来帮忙侦测,我们只要坐等通知就好了,大家快把它学起来,当个懒惰的工程师吧!


<<:  Day22 - 针对 Metasploitable 3 进行渗透测试(3) - Msfvenom 与 multi/handler

>>:  Day 22 Password Attacks - 密码攻击(hashcat)

[开发经验分享]如何中断执行中的 Task(任务)

情境 在做大数据分析时,由於需要从几千万甚至几亿笔资料中做运算,应用程序就整个不能动,若中间机器有要...

【Day30】30天的分享,是结束或是开始由你决定!

#odoo #开源系统 #数位赋能 #E化自主 总结 终於来到了30天IT铁人赛的最终回,这30天内...

#3 Python教学2

基本运算子 最最基本的运算子-赋值运算子 「=」 是最基本的运算子,它的作用是将 「=」 右方的数值...

[DAY 4] _ 用Keil5直接编写暂存器操控MCU的GPIO口_(建Keil5环境)

我今天来讲下如何看手册操作暂存器好了,就以简单的GPIO口hi low就好,我手边刚有STM32F4...

Day 09 CORS 跨来源资源共用

阿修的说文解字 何谓 CORS? MDN 大大表示: CORS(Cross-Origin Resou...