滚动到才加载:用 Intersection Observer 给长页面做图片懒加载

举报
海是岛思念的泪 发表于 2026/10/04 10:46:21 2026/10/04
【摘要】 用 Intersection Observer 给长页面做图片懒加载:data-src 存真地址、进视口才赋值 src、加载完 unobserve,含 rootMargin 提前加载、threshold 阈值、完整片段与老浏览器降级等注意点。

电商详情页、图集、长博客,图片一多,首屏就得把几十张图全下完。用户还没滚到下面,光等图片就白屏或转圈半天。更糟的是,那些用户根本没看到的图也占着带宽和流量,移动端尤其浪费。懒加载(lazy loading)就是治这个的:图片先不请求,等它快滚进可视区域了再下。早年的做法是在 scroll 事件里用 getBoundingClientRect 算每个图相对视口的位置,算得勤了卡主线程,算得疏了又不准。现在浏览器给了原生方案 Intersection Observer,专门负责"某个元素进没进视口、交叉了多少",不用我们手算坐标。

一、Intersection Observer 到底干了啥

它像一个"观察者":你告诉它"盯着这几个元素",再给个回调。浏览器会在合适的时机(通常是渲染空闲)主动通知你:“这几个元素现在和视口交叉了,交叉比例是 X”。你完全不用监听 scroll、不用读布局。对比 scroll 方案,它的好处有两点:一是不强制你每次滚动都跑 JS 算位置,省主线程;二是浏览器自己掌握了回调时机,批量处理更高效。回调收到的 entries 是个数组,每项带 isIntersecting(是否交叉)、intersectionRatio(交叉比例)、target(被观察的元素)。

二、最小实现

HTML 里图片先不写 src,改用一个自定义属性 data-src 存真实地址,src 暂时指向一张极小的占位图(纯色或模糊缩略都行):

<img class="lazy"  alt="示例">

JS 部分:

const imgs = document.querySelectorAll('img.lazy')
const io = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (!entry.isIntersecting) continue
    const img = entry.target
    img.src = img.dataset.src        // 进视口才真正去下载
    img.classList.remove('lazy')
    io.unobserve(img)                
  }
})
imgs.forEach((img) => io.observe(img))

逻辑就三行关键:判断 isIntersecting 为真 → 把 data-src 赋给 src 触发下载 → unobserve 取消观察。unobserve 很重要,否则元素一直在被观察,以后每次滚动浏览器都还通知你一次,白耗。占位图要足够小,不然懒加载省下的带宽又被占位图吃回去。

三、提前一点、或可设阈值

默认是"刚碰到视口边缘"才触发,用户快速滚动时可能看到图在转圈才出来。用 rootMargin 把触发区域往外扩 200px,元素还没进视口就提前下:

new IntersectionObserver(cb, { rootMargin: '200px' })

想更细地控制"露出多少才加载",用 threshold 设交叉比例,比如 0.1 表示露出 10% 才触发,传数组能分多档回调:

new IntersectionObserver(cb, { threshold: [0, 0.1, 0.5, 1] })

root 还能指定某个滚动容器(默认是视口),做内部滚动区的懒加载时用得上。

四、一个能直接用的完整片段

把结构和样式合一起,占位用纯色背景,图片加载完淡入:

<style>
  img.lazy { width: 100%; background: #eee; min-height: 200px; }
  img.lazy:not(.loaded) { opacity: 0; }
  img.loaded { opacity: 1; transition: opacity .3s; }
</style>
<img class="lazy"  alt="图片示例">
<script>
  const io = new IntersectionObserver((es) => {
    for (const e of es) {
      if (!e.isIntersecting) continue
      const img = e.target
      img.onload = () => img.classList.add('loaded')
      img.src = img.dataset.src
      io.unobserve(img)
    }
  })
  document.querySelectorAll('img.lazy').forEach((i) => io.observe(i))
</script>

onload 里加 loaded 类触发淡入,体验更顺。

五、几个要注意的点

第一,给 img 设好宽高或 aspect-ratio,否则加载前后高度变化会让下面的内容猛地跳动,体验很差。第二,首屏以上的图(above the fold)别加 lazy,直接正常加载,避免一打开看到一片占位灰块。第三,data-src 里的地址要可信,别直接拼用户输入,防止被塞进奇怪的协议。第四,老浏览器不支持 IO 时降级成全加载,用 'IntersectionObserver' in window 判断,没有就直接把所有 data-src 赋给 src。第五,原生 <img loading="lazy"> 也能懒加载,但 Intersection Observer 更可控、还能用于"曝光统计""无限滚动"等场景,按需选。

小结

图片懒加载用 Intersection Observer 最省心:data-src 存真地址、进视口才赋值 src、加载完 unobserve。配 rootMargin 提前加载、threshold 控制露出比例,比手算 scroll 位置简单也流畅。占位图要小、首屏图别懒、设好尺寸防跳动,老环境再降级。需要更自动的方案也可以用原生 loading=“lazy”,但 IO 在控制和复用上更强。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。