前端性能优化实战:用 IntersectionObserver 实现图片懒加载

一、引言

如果你做过内容型页面(电商列表、图文社区、后台管理、营销落地页),一定遇到过这些问题:

  • 页面首屏加载慢
  • 图片请求过多
  • 用户根本没滚到下面,却已经下载了几十张图片

过去我们常用 scroll + getBoundingClientRect 去判断元素是否进入视口,但这种方式:

  • 高频触发
  • 容易造成主线程压力
  • 代码维护成本高

IntersectionObserver API 的出现,本质上就是浏览器帮我们做了"可见性监听",并且是 异步且性能友好 的方式。


二、需求背景

真实业务场景通常长这样:

  • 商品列表:一次渲染 100+ 图片
  • feed流(小红书发现页、微博首页推荐):无限滚动加载

传统加载方式:

html 复制代码
<img src="real-image.jpg" />

问题是:

  1. 页面初始化瞬间请求过多
  2. 占满带宽影响关键资源
  3. 首屏指标(LCP / FCP)下降

我们希望做到:

  • 图片 进入可视区域附近 才加载
  • 加载逻辑对业务无侵入
  • 可配置、可复用

三、工作原理(核心理解)

1️⃣ IntersectionObserver API 是什么

简单说:

浏览器帮你监听某个元素是否进入视口。

它通过异步计算元素与 viewport(或父容器)的交集,而不是依赖 scroll 事件轮询。


2️⃣ IntersectionObserver API 三个关键概念

root

观察的根节点:

  • null → 浏览器视口
  • 某个滚动容器 → 局部懒加载

rootMargin(非常重要)

提前触发加载的"预加载区域"。

例如:

js 复制代码
rootMargin: "100px"

表示图片距离视口还有 100px 时就开始请求。

实战经验:100~300px 是比较平衡的值。


threshold

触发比例。

js 复制代码
threshold: 0.1

表示元素有 10% 可见时触发。


3️⃣ 为什么比 scroll 更快?

因为:

  • 浏览器内部优化
  • 批量计算
  • 回调异步执行
  • 避免频繁 layout

这也是为什么它被广泛用于懒加载、曝光统计、无限滚动。


四、代码实现

下面是一套我在实际项目里常用的实现。


1.HTML 结构

javascript 复制代码
// 注意:真正的图片地址放在 data-src
<img class="imgs" data-src="https://example.com/a.jpg" alt="" />

2.初始化 Observer

javascript 复制代码
const observer = new IntersectionObserver(
  (entries, obs) => {
    entries.forEach((entry) => {
      if (!entry.isIntersecting) return

      const img = entry.target

      // 真正加载图片
      img.src = img.dataset.src

      // 加载完成后取消观察(关键优化)
      obs.unobserve(img)
    })
  },
  {
    root: null,
    rootMargin: "200px 0px",
    threshold: 0.01,
  }
)

3.开始观察

javascript 复制代码
document.querySelectorAll(".imgs").forEach((img) => {
  observer.observe(img);
});

4.失败兜底 + 占位图

javascript 复制代码
img.onerror = () => {
  img.src = "/images/fallback.png";
};

五、解决了哪些痛点?

✅ 1. 首屏速度明显提升

非首屏图片不加载,减少初始网络请求。


✅ 2. JS 开销下降

不用自己写:

javascript 复制代码
window.addEventListener("scroll", handler);

避免频繁计算。


✅ 3. 自动资源释放

使用:

javascript 复制代码
observer.unobserve(img);

监听一次即销毁,避免内存累积。


✅ 4. 代码可维护

  • 可封装成 hook / directive
  • Vue / React 都能直接复用

六、竞品方案分析

方案 1:scroll + throttle

优点

  • 兼容极老浏览器

缺点

  • 高频触发
  • 需要手动计算
  • 易产生性能问题

方案 2:原生 loading="lazy"

html 复制代码
<img loading="lazy" src="a.jpg" />

现代浏览器已经支持。

优点

  • 一行代码
  • 几乎零成本

缺点

  • 无法精准控制
  • 自定义行为有限
  • 某些复杂场景不足

方案 3:IntersectionObserver(推荐)

优势

  • 可控性强
  • 性能优秀
  • 支持预加载区
  • 可扩展为埋点 / 曝光统计

兼容率已接近 96%(现代浏览器基本无压力)。


⭐ Observer 尽量复用

不要每个图片 new 一个。


⭐ 搭配 skeleton 更丝滑

体验远比 loading spinner 好。


竞品性能分析

方案 触发机制 主线程压力 Scroll 流畅度 复杂度 典型问题 适合场景
IntersectionObserver 浏览器异步计算可见性 ⭐ 低 ⭐⭐⭐⭐⭐ 老浏览器需 polyfill 图片懒加载、Feed 流、曝光统计
scroll + getBoundingClientRect scroll 高频事件 ❗ 高 ⭐⭐ 高频计算导致卡顿 小型页面 / 简单需求
requestAnimationFrame 轮询 每帧执行检测 ❗ 高 ⭐⭐ 与渲染帧竞争资源 动画相关场景
原生 loading="lazy" 浏览器内置策略 ⭐ 极低 ⭐⭐⭐⭐⭐ ⭐ 最低 控制能力有限 简单图片懒加载

九、总结

一句话总结:

IntersectionObserver API 把"监听滚动"这件事交回给浏览器,是真正工程级的懒加载方案。

它不仅适用于图片,还可以扩展到:

  • 无限滚动
  • 曝光埋点
  • 视频自动播放
  • 动画触发

作者: 王新焱

博客: https://blog.csdn.net/qq_34402069

时间: 2026年2月14日


相关推荐
q1cheng2 小时前
基于Spring Boot + Vue项目online_learn的权限控制机制分析
前端
扶苏10022 小时前
深入理解 Vue 3 的 watch
前端·javascript·vue.js
前端 贾公子2 小时前
组件 v-model 的封装实现原理及 Input 组件的核心实现(上)
服务器·前端·javascript
weixin199701080162 小时前
亚马逊商品详情页前端性能优化实战
前端·性能优化
全栈前端老曹2 小时前
【Redis】 监控与慢查询日志 —— slowlog、INFO 命令、RedisInsight 可视化监控
前端·数据库·redis·缓存·全栈·数据库监控·slowlog
扶苏10022 小时前
Vue 3 的组合式 API(Composition API)优势
前端·javascript·vue.js
万少2 小时前
这可能是程序员离用AI赚钱最容易的一个机会了
前端·ai编程
范什么特西2 小时前
狂神---死锁
java·前端·javascript
weixin199701080162 小时前
易贝(eBay)商品详情页前端性能优化实战
前端·性能优化