2022更文挑战1-5分钟看懂intersectionObserver实现懒加载

166 阅读4分钟

前言

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战

以前我们实现一个图片懒加载,总会想到通过监听页面的scroll事件,判断页面的滚动高度与元素离页面顶部的高度是否少于一个值,从而判断图片是否在可视区域内然后去加载图片这样的方案去实现图片懒加载,这样的方案计算量比较大,很容易出现性能问题。

现在出了一个新的api叫《intersectionObserver》,即重叠观察者,它提供了一种异步观察目标与其祖先元素或顶部文档视窗交叉的方法,帮助我们很容易的判断页面和观察元素是否交叉,从而去实现我们想要做的逻辑,如图片懒加载,或者监听指定元素页面曝光等需求。

定义

先看看mdn上的描述:

IntersectionObserver()构造器创建并返回一个IntersectionObserver对象。 如果指定rootMargin则会检查其是否符合语法规定,检查阈值以确保全部在0.0到1.0之间,并且阈值列表会按升序排列。如果阈值列表为空,则默认为一个[0.0]的数组。

语法:

var observer = new IntersectionObserver(callback[, options]);

参数:

options 可选
一个可以用来配置observer实例的对象。如果options未指定,observer实例默认使用文档视口作为root,并且没有margin,阈值为0%(意味着即使一像素的改变都会触发回调函数)。你可以指定以下配置:
 1. root
 监听元素的祖先元素Element对象,其边界盒将被视作视口。目标在根的可见区域的的任何不可见部分都会被视为不可见。
 2. rootMargin
 一个在计算交叉值时添加至根的边界盒(bounding_box (en-US))中的一组偏移量,类型为字符串(string) ,可以有效的缩小或扩大根的判定范围从而满足计算需要。语法大致和CSS 中的margin 属性等同; 可以参考 The root element and root margin in Intersection Observer API来深入了解margin的工作原理及其语法。默认值是"0px 0px 0px 0px"3.threshold
 规定了一个监听目标与边界盒交叉区域的比例值,可以是一个具体的数值或是一组0.01.0之间的数组。若指定值为0.0,则意味着监听元素即使与根有1像素交叉,此元素也会被视为可见. 若指定值为1.0,则意味着整个元素都在可见范围内时才算可见。可以参考Thresholds in Intersection Observer API 来深入了解阈值是如何使用的。阈值的默认值为0.0。
 
 常用配置 : 
 var observerOptions = {
    threshold: .5, //目标元素与视窗重叠的阈值(0~1)
    root:null // 目标视窗即目标元素的父元素,如果没有提供,则默认body元素
}

使用方法

1.创建观察者

observer配置项
var observerOptions = {
    threshold: .4, //目标元素与视窗重叠的阈值(0~1)
    root:null // 目标视窗即目标元素的父元素,如果没有提供,则默认body元素
}

//observer 回调函数
const observerCallback = (entries) => {
    entries.forEach(item => {
       /*
        * item.time 记录交叉发生的相应的时间,毫秒
        * item.rootBounds:用来描述交叉区域观察者的根
        * item.boundingClientRect:返回包含目标元素的边界信息,作用于getBoundingClientRect()类似
        * item.intersectionRect:目标元素与视口(或根元素)的交叉区域的信息
        * item.isIntersecting:用于判断目标元素与根元素是否相交,true为相交,false为不相交或者从交叉到不交叉的状态
        * item.intersectionRatio:目标元素与视口(或根元素)的相交比例。
        * item.target:与根出现相交区域改变的元素
        */
        // 当前元素可见
        if(item.isIntersecting){ 
            {
                ...这里是你的业务逻辑
            }
            // 解除观察当前元素 避免重复观察。
            io.unobserve(item.target) 
        }	
    })
}

2. 传入观察者

//如果是有多个图片,获取完图片后循环图片并且添加观察实例

let imgs = [...document.querySelectorAll(".img")];

const observe = new IntersectionObserver(callback); // 实例化 IntersectionObserver

imgs.forEach((image) => {
    observe.observe(image); // observe : 被调用的IntersectionObserver实例。给每个图片添加观察实例
});

图片懒加载使用案例

html 

<div class="box">
      <ul>
        <li>
          <img class="img" data-src="./coffe.jpg" src="./preview.png" alt="" />
        </li>
        <li>
          <img class="img" data-src="./coffe.jpg" src="./preview.png" alt="" />
        </li>
        <li>
          <img class="img" data-src="./coffe.jpg" src="./preview.png" alt="" />
        </li>
        <li>
          <img class="img" data-src="./coffe.jpg" src="./preview.png" alt="" />
        </li>
        <li>
          <img class="img" data-src="./coffe.jpg" src="./preview.png" alt="" />
        </li>
        <li>
          <img class="img" data-src="./coffe.jpg" src="./preview.png" alt="" />
        </li>
        <li>
          <img class="img" data-src="./coffe.jpg" src="./preview.png" alt="" />
        </li>
        <li>
          <img class="img" data-src="./coffe.jpg" src="./preview.png" alt="" />
        </li>
        <li>
          <img class="img" data-src="./coffe.jpg" src="./preview.png" alt="" />
        </li>
        <li>
          <img class="img" data-src="./coffe.jpg" src="./preview.png" alt="" />
        </li>
        <li>
          <img class="img" data-src="./coffe.jpg" src="./preview.png" alt="" />
        </li>
        <li>
          <img class="img" data-src="./coffe.jpg" src="./preview.png" alt="" />
        </li>
      </ul>
</div>

script
  //先获取所有img的dom
  let imgs = [...document.querySelectorAll(".img")];
  //定义observer回调
  const callback = (entries) => {
    // entries 是观察的元素数组
    entries.forEach((ele) => {
      if (ele.isIntersecting) {
        // isIntersecting  是否被观察到,true表示相叉。
        const data_src = ele.target.getAttribute("data-src"); 
        ele.target.setAttribute("src", data_src); // ele.target 是目标元素
        observe.unobserve(ele.target); // 真实地址替换后 取消对它的观察,避免重复观察
      }
    });
  };
  
  // 实例化 IntersectionObserver
  const observe = new IntersectionObserver(callback);
  
  imgs.forEach((image) => {
    observe.observe(image); // observe : 被调用的IntersectionObserver实例。给每个图片添加观察实例
  });

通过上面的案例我们可以很容易的实现图片懒加载的功能,这样就可以避免使用监听scroll页面滚动的事件里通过大量的计算去实现功能,有效的提升了页面的加载性能。当然这个api不仅限于图片懒加载,还可以用于做页面元素的曝光,或者元素的吸顶,吸底等效果,即判断当前是否与顶部元素相叉,如果不相叉的时候,把需要吸顶的元素设置为吸顶。