图片懒加载从原理到实践:Intersection Observer方案与性能优化
我做过不少前端性能优化图片懒加载算是最常见、也最容易出效果的一项改动。很多项目首屏图片十几张全部加载完要等好几秒用户早滑走了。把图片懒加载落地之后页面首屏耗时能降下来一大截尤其是长列表、电商商品页、内容社区这类图片密集的场景效果明显得让人有点意外。这篇文章不打算讲那种“调用一个库就完事”的流程而是把图片懒加载从原理到生产级的实现细节拆开讲一遍。包括我为什么最终选了 Intersection Observer 方案、原生 loading 属性到底够不够用、怎么处理布局抖动、怎么跟 CDN 和图片裁剪服务搭配还有线上踩过的几个坑和排查套路。适合正在做前端性能优化、或者想搞懂懒加载背后原理的开发者参考。大佬可以重点看后两章的细节取舍新手建议从头到尾顺着走一遍。1. 图片懒加载到底在解决什么问题1.1 从一次页面卡顿说起之前接手过一个内容资讯站点的改版列表页单页有接近 40 张配图每张图都是 1200px 宽的 JPEG大小在 200KB 到 600KB 之间。首屏加载的时候浏览器一股脑把全部图片都请求了光图片传输就差不多 10MB。弱网环境下页面要等很久才能稳定响应滚动的时候还会出现图片逐个加载导致的明显卡顿。当时盯着监控面板发现首屏吞吐量高得离谱而用户真正看到的只有前两三张图的区域后面三十多张图全部是白白浪费掉的流量。这就是图片懒加载要解决的第一个问题没进入视口的图片本来就不该被请求。用户想看哪一块内容浏览器再动态去加载那部分的图片资源。它不是一个炫技功能而是对网络资源和用户流量的基本尊重。1.2 懒加载的核心矛盾与收益懒加载的本质是把“加载时机”从页面初始化转移到“元素接近视口时”。这里有一个提前量的取舍加载太早会白白占用网络带宽加载太晚用户滚动到图片位置时会出现白屏等待体验变差。所以判断“什么时候开始加载”就成了所有方案的核心矛盾。实操后收益可以从几个维度量化首屏图片请求数减少页面 load 事件和 LCP 指标都有改善。总传输体积下降对弱网用户特别友好也能省 CDN 流量费用。滚动过程中如果配合占位比例能消除图片加载引起的布局偏移CLS 会更稳。如果站点本身图片不多、都是首屏必须展示的核心图懒加载收益不大还可能因为判断逻辑增加额外复杂度。所以在给项目做优化前我会先看一眼页面结构图片分布远离首屏、数量大于 10 张的懒加载基本稳赚。1.3 懒加载与响应式、占位图之间的边界懒加载经常和响应式图片、占位图混在一起说但它们解决的是不同问题。响应式图片解决的是“在不同屏幕尺寸下选哪张图”的问题懒加载解决的是“什么时候加载”的问题占位图解决的是“加载过程中给用户看什么”的问题。一个完整方案通常三件事都做先用srcset和sizes让浏览器按视口宽度选图再通过懒加载把真正选图的时机挪到即将进入视口时同时用 CSS 比例占位避免内容跳动。我在项目里见过不少只做了懒加载、却忽略了占位比例的页面。图片加载完成那一刻高度从 0 撑开下面的内容被挤下去用户鼠标点到的位置也跟着变了。这种情况在手机端尤其明显。所以做懒加载不能只盯着“省流量”还得把“加载后的形态稳定”一并解决掉。2. 实现方案选型与原理拆解2.1 原生 loadinglazy首选但不万能HTML 标准里直接提供了图片懒加载属性img loadinglazy。浏览器看到这个属性后会自动判断图片距离视口的远近在适当时候加载。好处是零依赖、零 JavaScript浏览器内部实现通常还做了预加载距离优化不会出现图片快进入视口时才紧急请求的问题。但原生属性也有一些限制。一是控制力度很弱你没法自定义提前加载的阈值也没法在图片即将进入视口前做自定义占位动画二是部分旧版本浏览器不识别这个属性会直接忽略图片照常加载这在兼容性要求高的对内系统里需要谨慎三是它不太方便接“加载失败重试”这类业务逻辑毕竟浏览器不会给你暴露详细的加载状态事件。所以我现在的基本选型规则是如果项目是纯内容展示页、兼容性要求不高直接用loadinglazy最省事如果项目需要统一体验、埋点统计、失败重试或者要兼容到较老的内核浏览器就自己用 Intersection Observer 实现。2.2 Intersection Observer现代主流方案IntersectionObserver让“元素是否进入视口”的监听变得非常高效。老方案是用 scroll 事件配合getBoundingClientRect()计算距离但滚动事件触发频率极高每次都做几何计算会有不必要的性能开销。Intersection Observer 由浏览器原生实现采用异步回调机制在元素可见性变化时再通知开发者性能表现明显更好。核心思路是先观察所有带有>div classlazy-image styleaspect-ratio: 16 / 9; img classlazy-image__img >const lazyImages document.querySelectorAll(img[data-src]); const imageObserver new IntersectionObserver((entries, observer) { entries.forEach((entry) { if (entry.isIntersecting) { const img entry.target; img.src img.dataset.src; img.classList.add(is-loaded); observer.unobserve(img); } }); }, { rootMargin: 200px 0px, threshold: 0.01 }); lazyImages.forEach((img) imageObserver.observe(img));rootMargin: 200px 0px表示在视口上下各扩展 200px 作为触发区域相当于提前预测用户滚动方向让图片在即将进入视口前就开始加载用户实际看到时已经加载完成或接近完成。阈值0.01表示元素只要有 1% 进入这个扩展区域就触发这样不会要求图片完整露出来才加载体验更顺滑。这里有一个容易忽略的点threshold和rootMargin搭配时如果threshold设置过大比如0.5图片必须一半区域可见才触发明显太晚设置成0.01基本等效于“开始进入即触发”。3.3 响应式图片data-srcset 与>img classlazy-image__img >if (entry.isIntersecting) { const img entry.target; img.src img.dataset.src; if (img.dataset.srcset) { img.srcset img.dataset.srcset; } if (img.dataset.sizes) { img.sizes img.dataset.sizes; } img.classList.add(is-loaded); observer.unobserve(img); }这样浏览器会在图片真正开始加载时结合当前视口宽度和sizes规则自主选择合适的候选图既不额外增加请求也不会选错分辨率。需要注意>function loadImage(img, retryCount 0) { return new Promise((resolve, reject) { const handleLoad () { img.removeEventListener(load, handleLoad); resolve(); }; const handleError (e) { img.removeEventListener(error, handleError); reject(e); }; img.addEventListener(load, handleLoad); img.addEventListener(error, handleError); img.src img.dataset.src; }); } async function handleLoad(img, observer, retryCount 0) { try { await loadImage(img); img.classList.add(is-loaded); observer.unobserve(img); } catch (err) { if (retryCount 2) { setTimeout(() handleLoad(img, observer, retryCount 1), 1500); } else { img.classList.add(is-error); // 这里可以替换为自定义错误占位图 } } }重试间隔 1.5 秒最多重试 2 次避免无限重试对 CDN 造成额外压力。在错误占位图上可以直接把src替换成一个内置的 SVG 错误提示图或者显示一个相近的底色图标不要让用户看到一片空白。这个细节虽然小但对内容站点的观感影响很大。还有性能细节懒加载的 Observer 实例最好全局只创建一个不要每个图片创建一个实例。图片数量多时可以分组观察但全局单例完全够用。也别忘了在所有图片加载完成后主动disconnect()释放监听资源。3.5 封装成通用组件的扩展设计组件化时我会把懒加载能力封装成一个工厂函数或类支持配置项方便多个页面复用class LazyLoader { constructor(options {}) { this.options { root: null, rootMargin: 200px 0px, threshold: 0.01, selector: img[data-src], placeholderClass: is-loading, loadedClass: is-loaded, errorClass: is-error, ...options }; this.observer null; this.images []; } init() { this.images Array.from(document.querySelectorAll(this.options.selector)); if (IntersectionObserver in window) { this.observer new IntersectionObserver(this.onIntersect.bind(this), { root: this.options.root, rootMargin: this.options.rootMargin, threshold: this.options.threshold }); this.images.forEach((img) this.observer.observe(img)); } else { this.fallbackBindScroll(); } } onIntersect(entries, observer) { entries.forEach((entry) { if (entry.isIntersecting) { this.handleLoad(entry.target, observer); } }); } handleLoad(img, observer) { // 实际加载逻辑见上一小节 } fallbackBindScroll() { // 降级为滚动监听 } } const loader new LazyLoader(); loader.init();高内聚、低耦合的组件看起来简单但换页复用、埋点接入都会方便很多。比如我常在handleLoad里触发一个自定义事件让业务方自己决定要不要上报图片加载数据。这样组件本身不关心业务只关心加载行为后续维护成本显著降低。4. 加载占位、骨架屏与体验细节4.1 用 CSS 比例占位防止布局抖动前面提到用aspect-ratio或padding-bottom占位但真正的细节在于如何让占位和图片加载状态平滑衔接。最直接的做法是给图片加opacity过渡加载完成后再渐变显示.lazy-image { position: relative; background: #f0f0f0; overflow: hidden; } .lazy-image__img { opacity: 0; transition: opacity 0.3s ease; } .lazy-image__img.is-loaded { opacity: 1; }图片没加载前容器背景是浅灰高度按比例占住页面不会跳动加载完成后图片淡入视觉上很顺滑。这个方案比直接display: none到block的切换体验好得多。要注意background最好也用一个和图片主色调接近的颜色这样占位灰不会显得很突兀。对于不规则的瀑布流布局比例占位更复杂一些需要提前知道图片宽高比或者在服务端生成图片元数据并注入到模板里。我的经验是优先保证容器宽度确定高度用aspect-ratio直接由 CSS 计算避免 JS 参与测量减少布局抖动来源。4.2 渐进式加载与模糊预览比纯灰占位更高阶一点的方案是“模糊预览”。思路是先加载一张极小的压缩图通常几 KB铺在容器上并打上模糊滤镜等原图加载完成后再让高清图浮在它的上面。实现起来并不复杂img classlazy-image__thumb>const container document.querySelector(.scroll-list); const observer new IntersectionObserver(callback, { root: container, rootMargin: 200px 0px, threshold: 0.01 });这里有个关键前提滚动容器必须真的存在独立的滚动上下文并且容器尺寸变化时 observer 能正确计算交叉区域。若容器内的图片高度未定导致观察时机异常需要先保证占位高度设置完毕再初始化 observer。CLSCumulative Layout Shift是懒加载改造中必须关注的指标。很多改造后图片加载都正常但 CLS 反而变差原因就是占位缺失。在改造前后对比监控时我会同时对比 CLS 和 LCP。这两个指标一个管视觉稳定性一个管首屏加载速度懒加载做得好两者应该一起变好或至少不变差。4.4 量化收益怎么证明这次优化有效做性能优化最怕“感觉变快了”要有数据支撑。我在项目里会在同一个页面测试三种状态全部图片直接加载、原生loadinglazy、自研 Intersection Observer 懒加载。分别记录首屏网络请求数总传输体积LCP 时间滚动流畅度通过长任务统计CLS 数值一个具体案例是某资讯页改造后首屏请求数从 42 降到 16总体积从 9.8MB 降到 3.2MB弱网下的 LCP 从 3.8 秒降到 1.9 秒。这个数据让我有信心继续推广到更多页面。建议在博客或述职里展示这种对比数据比空谈“性能更好”更有说服力。5. 常见问题与排查技巧实录5.1 图片不触发加载定位思路最典型的故障是页面滚动到图片位置但图片始终不加载控制台里看不到请求。第一反应先确认图片元素是否真的被 observer 观察到了。可以在回调里临时打印entry.isIntersecting和图片的边界信息。常见原因有几个图片在初始化时display: noneobserver 无法正确计算交叉区域等它变为可见时也不会自动触发。图片元素被某个overflow: hidden的不可见容器遮挡或者祖先元素高度为 0。图片已经加载过一次但被unobserve后续重新操作 DOM 导致元素副本没有被正确观察。rootMargin设置不当比如设成了-200px导致图片必须完全进入视口后才触发。排查时我会用开发者工具的 Elements 面板选中图片查看其src和>

相关新闻

本地同城信息系统(社交+资讯+分类信息+商家入驻+本地服务)源码怎么选?

本地同城信息系统(社交+资讯+分类信息+商家入驻+本地服务)源码怎么选?

本地同城信息系统源码选型与落地全案选同城信息系统源码,最容易踩的坑不是“功能少”,而是上线后才发现系统扛不住日常流量、核心业务模块改不动、想做本地化运营时处处受限于通用模板。真正能跑通长期运营的选型逻辑,不能只看演示页的界面美…

2026/10/11 19:53:47 阅读更多 →
学生选课系统:SQL Server数据库课程设计实战指南

学生选课系统:SQL Server数据库课程设计实战指南

简介:本资源是一份完整的数据库系统课程设计报告模板,面向高校计算机、软件工程等专业本科生,用于支撑《数据库系统概论》类课程的实践教学与课程设计作业。报告以“学生选课管理信息系统”为案例,系统覆盖需求分析(含…

2026/10/11 19:52:46 阅读更多 →
Photoshop习题版PDF:用可验证的题海训练打磨抠图调色肌肉记忆

Photoshop习题版PDF:用可验证的题海训练打磨抠图调色肌肉记忆

简介:这是一份精选的Photoshop基础习题资料,面向刚接触图像处理的初学者和需要巩固基础的设计爱好者,将颜色模式、位图与矢量图、图像文件格式、像素与分辨率、画布与图像尺寸等核心概念以问答形式系统梳理,并涵盖旋转画布、精确裁…

2026/10/11 19:52:46 阅读更多 →

最新新闻

PLC基本指令详解:从位逻辑到定时计数,掌握梯形图编程核心

PLC基本指令详解:从位逻辑到定时计数,掌握梯形图编程核心

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 3:19:56 阅读更多 →
柔性上料盘如何替代振动盘:从换型瓶颈到视觉引导的产线升级指南

柔性上料盘如何替代振动盘:从换型瓶颈到视觉引导的产线升级指南

简介:《全球与中国柔性上料盘市场现状及未来发展趋势(2024版)》是一份QYResearch出品的专业市场研究报告,面向柔性上料与自动化产线设备从业者、工业机器人厂商、市场分析师及投资研究人员。报告以2019至2023年为历史期、2024至20…

2026/10/12 3:19:56 阅读更多 →
WiFi分析工具设计实战:从数据采集到信道优化与故障排查

WiFi分析工具设计实战:从数据采集到信道优化与故障排查

1. 从一个标题说起:这个工具到底在解决什么问题第一次看到“Jev powered WiFi analysis tool”这个标题,我的直觉是:这大概率是一个把无线网络分析能力封装成轻量级工具的项目,名字里的“Jev”可能是作者自定的代号、模块名或者某…

2026/10/12 3:19:56 阅读更多 →
SpringBoot+Vue美食网站系统:前后端分离全栈项目架构与部署详解

SpringBoot+Vue美食网站系统:前后端分离全栈项目架构与部署详解

做个人项目这些年,前后端分离的练手项目做了不少,但每次有人让我推荐一个既能完整跑起来、又能覆盖主流开发流程的学习项目,我第一反应往往是这套美食网站系统。为什么?因为它的技术选型非常贴近当下中小型项目的真实组合&#xf…

2026/10/12 3:19:56 阅读更多 →
高斯赛德尔迭代法:大规模稀疏线性方程组的工程解法与实战技巧

高斯赛德尔迭代法:大规模稀疏线性方程组的工程解法与实战技巧

线性方程组这东西,刚接触数值计算的时候,总觉得不是事——高斯消元一把梭,n100也就是眨眨眼的事。可等你真在工程里碰到几十万未知量、矩阵非零元稀稀落落排成带状或块状的时候,直接法的“快”就变成了一种幻觉:要么内…

2026/10/12 3:19:56 阅读更多 →
attrs 比较机制完全指南:默认相等性、排序生成与自定义比较(Comparison)

attrs 比较机制完全指南:默认相等性、排序生成与自定义比较(Comparison)

后端 【免费下载链接】attrs Python Classes Without Boilerplate 项目地址: https://gitcode.com/gh_mirrors/at/attrs 点击查看 免费下载 本文围绕 attrs 官方文档 docs/comparison.md 展开,系统讲解 attrs 类实例的相等性(equality&#…

2026/10/12 3:18:56 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →