还在为网页图片太多导致加载卡顿发愁?电商页面图片密密麻麻,首屏加载半天没反应?别慌!今天教你一招 “图片懒加载”,让页面加载速度提升 50%,初学者也能轻松上手~
一、为什么需要图片懒加载?先搞懂浏览器加载的 “痛点”
打开一个图片超多的网页(比如电商首页),你有没有发现:明明只看得到屏幕内的几张图,浏览器却在疯狂下载所有图片?这背后藏着性能 “大坑”!
(一)图片加载的 “秘密流程”
当你在<img>
标签里写src="图片地址"
时,浏览器会:
- 解析到
src
属性,立刻启动下载线程(浏览器通常有多个线程并发下载资源); - 通过
http/https
协议(应用层协议),根据图片地址的 IP,向服务器发送请求; - 经过 TCP/IP 协议传输数据,把图片字节码下载到本地,再渲染显示。
但问题来了:网络带宽是有限的!如果一上来就加载所有图片,会抢占 CSS、JS 等关键资源的下载通道,导致页面加载变慢。数据显示,页面加载延迟 0.5 秒,用户流失率会增加 20%!
(二)直接加载所有图片的 3 大问题
- 首屏加载慢:用户打开页面,要等所有图片下载完才能看到内容,体验差;
- 浪费流量:用户可能没滚动到页面底部,却下载了所有图片,尤其对移动端用户不友好;
- 阻塞关键资源:图片下载占用线程,导致 CSS 渲染、JS 执行被拖延,页面 “卡壳”。
二、图片懒加载:只加载 “该加载” 的图片
(一)核心思路:“按需加载”
懒加载的本质很简单:只加载用户当前能看到的图片(可视区),滚动到哪里再加载哪里。
比如你打开一个有 100 张图的页面,首屏只显示 5 张,就先下载这 5 张;当你往下滚动,再加载接下来进入视线的图片。
(二)实现关键:别用src
存真实地址!
<img>
标签的src
属性是 “触发下载” 的开关 —— 只要src
有值,浏览器就会立刻下载。所以懒加载的第一个技巧是:
- 真实图片地址存在自定义属性里(比如
data-original
),不直接写在src
里; src
暂时放一张占位图(比如小尺寸的 loading 动图),既占位又不占用太多带宽(占位图可以缓存,只下载一次)。
像这样:
<img
class="image-item"
lazyload="true" <!-- 标记这是需要懒加载的图片 -->
src="loading.gif" <!-- 占位图 -->
data-original="真实图片地址.jpg" <!-- 真实地址存在自定义属性 -->
/>
三、手把手实现懒加载:从基础版到高级版
(一)基础版:监听滚动事件,判断图片是否在可视区
核心逻辑:监听页面滚动,每次滚动时检查图片是否进入可视区,进入则把data-original
的值赋给src
,触发下载。
代码实现:
<!-- HTML结构 -->
<img class="image-item" lazyload="true" src="loading.gif" data-original="pic1.jpg" />
<img class="image-item" lazyload="true" src="loading.gif" data-original="pic2.jpg" />
<!-- 更多图片... -->
<script>
// 1. 获取所有需要懒加载的图片
const imgs = document.querySelectorAll('img[data-original][lazyload]');
//选中同时包含 data-original 和 lazyload 属性的 <img> 标签
// 2. 定义懒加载函数:检查并加载可视区图片
function lazyload() {
// 可视区高度(当前屏幕能看到的高度)
const viewHeight = document.documentElement.clientHeight;
// 遍历所有图片
imgs.forEach(img => {
// 如果已经加载过(没有data-original了),跳过
if (!img.dataset.original) return;
// 获取图片位置:getBoundingClientRect()返回元素相对于可视区的坐标
const rect = img.getBoundingClientRect();
// 判断图片是否进入可视区:图片顶部 < 可视区高度,且图片底部 > 0
if (rect.top < viewHeight && rect.bottom > 0) {
// 创建新Image对象预加载,避免直接修改src导致的闪烁
const newImg = new Image();
newImg.src = img.dataset.original;
// 图片加载完成后,替换src
newImg.onload = () => {
img.src = img.dataset.original; // 显示真实图片
img.removeAttribute('data-original'); // 标记为已加载
img.removeAttribute('lazyload');
};
}
});
}
// 3. 触发时机:初始加载+滚动时
window.addEventListener('scroll', lazyload); // 滚动时检查
document.addEventListener('DOMContentLoaded', lazyload); // 页面初始渲染后检查
window.addEventListener('resize', lazyload); // 窗口大小改变时检查
</script>
原理说明:
getBoundingClientRect()
:获取图片相对于可视区的位置,rect.top
是图片顶部到可视区顶部的距离,rect.bottom
是图片底部到可视区顶部的距离;- 当
rect.top < viewHeight
且rect.bottom > 0
时,说明图片有一部分在可视区内,需要加载。
(二)进阶版:解决滚动事件 “触发太频繁” 的问题
基础版有个缺陷:scroll
事件会在滚动时疯狂触发(一秒可能触发几十次),每次都遍历所有图片 + 调用getBoundingClientRect()
(会触发回流),可能导致性能问题。
解决办法:节流(throttle) —— 让函数在一段时间内只执行一次。
节流函数实现:
// 节流函数:每隔delay时间,只执行一次func
function throttle(func, delay = 100) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime >= delay) {
func.apply(this, args);
lastTime = now;
}
};
}
// 用节流包装懒加载函数
const throttledLazyload = throttle(lazyload);
window.addEventListener('scroll', throttledLazyload); // 滚动时只按间隔执行
效果:
滚动时,lazyload
函数不再频繁触发,而是每隔 100 毫秒执行一次,减少性能消耗。
(三)终极版:用IntersectionObserver
彻底解放性能
现代浏览器提供了IntersectionObserver
API,专门监听元素是否进入可视区,异步执行(不阻塞主线程),无需手动监听scroll
事件,也不用节流!
代码实现:
<script>
function initLazyload() {
// 获取所有需要懒加载的图片
const imgs = document.querySelectorAll('img[data-original][lazyload]');
// 创建观察器
const observer = new IntersectionObserver((changes) => {
changes.forEach((change) => {
// 当图片进入可视区(交叉比例>0)
if (change.intersectionRatio > 0) {
const img = change.target;
// 加载图片
const newImg = new Image();
newImg.src = img.dataset.original;
newImg.onload = () => {
img.src = newImg.src;
img.removeAttribute('data-original');
img.removeAttribute('lazyload');
observer.unobserve(img); // 加载完后停止观察,节省资源
};
}
});
});
// 观察所有图片
imgs.forEach(img => {
observer.observe(img);
});
}
// 页面加载后初始化
document.addEventListener('DOMContentLoaded', initLazyload);
</script>
优势:
- 无需监听
scroll
、resize
事件,代码更简洁; - 浏览器在后台异步计算元素是否可见,不阻塞 JS 执行;
- 自动处理性能优化,无需手动节流。
四、懒加载实战技巧:这些细节让体验更丝滑
-
占位图设计:用和真实图片相同尺寸的占位图(比如 1x1 像素的透明图拉伸),避免图片加载时页面布局 “跳动”;
-
预加载一点点:让图片在进入可视区前提前加载(比如距离可视区底部 200px 时就开始加载),用
IntersectionObserver
的rootMargin
实现:// 提前200px加载 const observer = new IntersectionObserver((changes) => { ... }, { rootMargin: '0px 0px 200px 0px' // 可视区底部向外扩展200px });
-
处理加载失败:给图片添加
onerror
事件,加载失败时显示默认图:newImg.onerror = () => { img.src = 'error.png'; // 加载失败显示错误图 };
-
兼容旧浏览器:
IntersectionObserver
在 IE 不支持,可降级使用基础版(配合节流)。
五、总结:懒加载让页面 “轻装上阵”
图片懒加载通过 “按需加载”,完美解决了多图页面的加载性能问题,核心步骤是:
- 真实地址存
data-original
,src
放占位图; - 监听图片是否进入可视区(基础版用
scroll
+ 节流,高级版用IntersectionObserver
); - 进入可视区后,将
data-original
赋值给src
,加载图片。