图片懒加载优化升级版

68 阅读3分钟

前言

在上次我们聊到前端性能优化之图片懒加载,虽然对性能有了一定的优化,但是还是有些地方可以再优化。那么今天我们再继续思考思考,哪里可以再优化的地方。

懒加载

懒加载技术是比较简单且容易理解的,我们先来回顾一下。

在页面需要加载大量图片的情况下,我们可以给每个img标签的src属性赋予一个默认加载的图片地址。由于src属性的特殊性,这是必填的并且浏览器会提前去下载src的图片。

如果我们把原来要展示的图片都直接给src的话,这会导致页面可能一下子无法把全部的图片显示出来。所以,我们让真正的图片地址赋给一个自定义data- 属性,等需要加载该图片的时候,就把data- 属性赋值给src,这样减轻了浏览器的压力。

目前,已经减轻了浏览器的压力,但是我们是通过判断滚动页面图片是否进入可视区来加载图片的。稍微细心点我们就能发现,轻轻滚动页面就会触发好几遍判断事件。我们再把节流方法加入这个优化方案里面,这样就已经达到了最佳。

/**
 * @param {Function} fn - 需要节流的函数
 * @param {number} wait - 时间间隔(毫秒)
 * @returns {Function} - 返回一个节流后的函数
*/
// 节流函数
function throttle(fn, wait) {
  let lastTime = 0
  return function (...args) {
    const now=Date.now()
    if (now - lastTime >= wait) {
      lastTime = now
      fn.apply(this.args)
    }
  }
}

好了,我们基本已经出色的完成了懒加载的功能。我们回顾一下是如何实现的。我们通过不断使用getBoundingClientRect()来判断是否进入了可视区。但是,getBoundingClientRect()会不断获取元素所在位置,即不断的触发回流重绘。这也也影响了我们的性能,我们要减少回流和重绘,所以就有了更优秀的方法。

屏幕截图 2025-06-27 225145.png

Intersection Observer

Intersection Observer这是浏览器的API,用于异步观察目标元素与其祖先元素或视口交叉的状态。我们无需手动监听滚动事件,浏览器原生支持观察原生是否进入视口,此方法性能更好,代码也更简洁。

基本用法

const observer=new Intersection Observer(callback,options)
  • callback:每当被观察的元素的可见性发生变化时,调用回调函数。
    • entries:被观察元素的列表,每个元素都是一个IntersectionObserverEntry对象
    • observer:IntersectionObserver实例本身
  • options:一个可选参数对象,用来配置观察器的行为
    • root:用于观察的祖先元素,默认为视口
    • rootMargin:根元素的外边距,用于扩大或缩小根元素的判断区域
    • threshold:规定在什么可见比例下触发回调的数组

Callback

const callback=(entries,observer)=>{
    entries.forEach(entry=>{
        if(entry.isIntersecting){
            // 元素进入可视区,加载图片
            const img=entry.target
            img.src=img.dataset.src
            // 停止观察该元素
            observer.unobserve(entry.targer)
        }
    })
}

Options

const options={
    root:null,  // 默认视口
    rootMargin:'0px',  // 设置外边距
    threshold:0.1   // 设置可见比例(0~1)的数组
}

完整实现

document.addEventListener('DOMContentLoaded', () => {
  const imgs = document.querySelectorAll('img[data-src]')
  const callback = (entries, observer) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target
        img.src = img.dataset.src
        observer.unobserve(img)
      }
    })
  }
  const options = {
    root: null,
    rootMargin: '0px',
    threshold: 0.1
  }
  const observer = new IntersectionObserver(callback, options)
  imgs.forEach(img => {
    observer.observe(img)
  })
})

Ending

以上就图片懒加载的优化升级版了,目前IntersectionObserver是现代浏览器的主流方法,性能比较优异。