在项目时发现了一个问题:一部分图片在没有进入viewpoint时依然会提前加载,捣鼓了半天后发现是
Nextjs
中Image
的lazyBoundary
默认值搞的鬼,它会给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为根节点的关系:
我们还可以给区域设置边界,可以扩展(正值)或缩减(负值)区域尺寸:
const options = {
rootMargin:'20px',
root:null //表示viewpoint
}
通过扩展相交区域,元素触碰绿色边界即视为相遇。
同理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)
});
在慢速滚动下的一次运行结果为:
一次快速滚动的运行结果:
只跟踪到了 0-0.3
,0.7-1
,0.3-0.5
,0
的变化,这是可以理解的,因为API尽可能地通知元素最新的变化。
Nextjs中仅加载进入可视区的图片
如何解决引言中提到的问题呢?我们需要把容器相交区域的边界(rootMargin
)设置为0px
,重置掉Nextjs的默认值:
<Image
className="next-image"
lazyBoundary="0px"
loading="lazy"
...
/>
完工!