图片懒加载的实现方案?IntersectionObserver 怎么用?

方案 1:原生 loading 属性(最简单)

<img src="image.jpg" loading="lazy" alt="..." />

方案 2:IntersectionObserver(推荐)

const images = document.querySelectorAll('img[data-src]')

const observer = new IntersectionObserver((entries, obs) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target
      img.src = img.dataset.src  // 加载真实图片
      img.removeAttribute('data-src')
      obs.unobserve(img)         // 加载后取消观察
    }
  })
}, {
  rootMargin: '200px'  // 提前 200px 开始加载
})

images.forEach(img => observer.observe(img))
<img data-src="image.jpg" src="placeholder.jpg" alt="..." />

IntersectionObserver API

const observer = new IntersectionObserver(callback, options)

// options
{
  root: null,         // 视口(默认)或指定容器
  rootMargin: '0px',  // 根元素的 margin
  threshold: 0.1      // 交叉比例达到 10% 时触发
}

// callback
(entries) => {
  entries.forEach(entry => {
    entry.isIntersecting  // 是否可见
    entry.intersectionRatio  // 交叉比例 0~1
    entry.target  // 被观察的元素
  })
}

// 方法
observer.observe(el)      // 开始观察
observer.unobserve(el)    // 停止观察
observer.disconnect()     // 停止所有观察

方案 3:scroll 事件 + getBoundingClientRect(传统)

function lazyLoad() {
  const images = document.querySelectorAll('img[data-src]')
  const scrollTop = window.pageYOffset
  const windowHeight = window.innerHeight
  
  images.forEach(img => {
    if (img.offsetTop < scrollTop + windowHeight + 200) {
      img.src = img.dataset.src
      img.removeAttribute('data-src')
    }
  })
}

window.addEventListener('scroll', throttle(lazyLoad, 200))
lazyLoad() // 初始检查

⚠️ 性能差:scroll 事件频繁触发,offsetTop 触发重排

应用场景

  1. 图片懒加载
  2. 无限滚动列表
  3. 组件懒渲染(滚到可见区域才渲染)
  4. 广告/视频按需加载
  5. 动画触发(元素进入视口时播放)
// 无限滚动
const observer = new IntersectionObserver(([entry]) => {
  if (entry.isIntersecting) {
    loadMore()
  }
})
observer.observe(document.querySelector('.sentinel'))

最佳实践

同分类其他题目