优化Nextjs图片懒加载:Intersection API

1,954 阅读3分钟

在项目时发现了一个问题:一部分图片在没有进入viewpoint时依然会提前加载,捣鼓了半天后发现是NextjsImagelazyBoundary默认值搞的鬼,它会给viewpoint扩展200px,导致那些本不在可视范围的图片提前触发了加载事件。它的底层实现是基于Intersection的,于是带着些许的困惑,开始了对Intersection API的研究。

Intersection API简介

准确的说法叫:Intersection Observer API,它提供了一组API,让开发者可以更好、更方便地检测、判断盒子相交状态的变化。它主要的应用是图片的懒加载、无限滚动等。实现的本质是通过检测当盒子进入到可视区后,触发相应的操作。

这里的盒子的DOM层级必须是上层和下层关系:要么是viewpoint与普通元素,要么父子节点。

除了以上两种节点外,还需要一个桥梁,这就是Observer

比如,我们可以监测div盒子在可视区域中的位置变化:

const target = divEle;
const options = {
    root:null //null代表viewpoint
}
const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if(entry.isIntersecting){
        //进入了可视区
        target.style.backgroundColor = 'red'
    }else{
        //离开了可视区
    }
  });
}, options);
//可以观察多个,这里我们只观察了一个div
observer.observe(target)

当不需要监听某个元素时记得调用:Observer.unobserve。 如果需要解除所有元素的监听则调用:Observer.disconnect()

为何要用Intersection API

其实现有的很多场景,其他方式也是可以实现的。比如懒加载可以通过滚动监听页面scrollTop值结合窗口的尺寸判断元素是否进入可视区。

缺点时滚动事件触发非常密集,又是运行在主线程上的,可能存在性能问题而导致页面卡顿,这个问题在移动端会更加明显。

而Intersection API采用了异步回调的方式,避免阻塞主进程。可监测的细节也更为丰富,如特定元素的进入和离开、进入的比例等。

上层容器的相交区

这里的相交区指定了目标节点触发相交的区域范围。

普通的以viewpoint为根节点的关系: image.png

我们还可以给区域设置边界,可以扩展(正值)或缩减(负值)区域尺寸:

const options = {
  rootMargin:'20px',
  root:null //表示viewpoint
}

image.png 通过扩展相交区域,元素触碰绿色边界即视为相遇。 同理rootMargin:'-20px'可以缩小相交区域从而达到延迟相交的目的。

注意:上述边界并不改变盒模型的尺寸,只是修改了监听区域的尺寸。

以上都是针对目标节点在可视区内的变化,其实换做普通容器也是一样的道理,如果容器存在内边距、边框,需要注意的是,相交区域对应的是容器的内容盒子

阈值的设置

除了可以检测盒子在容器中进入和离开以外,我们还可以通过一些阈值(threshold)的设定来监测目标节点的相交比例。比如我们要监测div在进入到可视区一半的情况:

const options = {
  root: null,
  threshold:[0,0.3,0.5,0.7,1],//这里设定了几个阈值,相交比例在0-0.3、0.3-0.5、0.5-0.7、0.7-1之间会各触发一次
};
...
entries.forEach((entry) => {
  console.log(entry.intersectionRatio)
});

在慢速滚动下的一次运行结果为:

image.png

一次快速滚动的运行结果:

image.png

只跟踪到了 0-0.30.7-10.3-0.50的变化,这是可以理解的,因为API尽可能地通知元素最新的变化。

Nextjs中仅加载进入可视区的图片

如何解决引言中提到的问题呢?我们需要把容器相交区域的边界(rootMargin)设置为0px,重置掉Nextjs的默认值:

<Image
  className="next-image"
  lazyBoundary="0px"
  loading="lazy"
  ...
/>

完工!