前言:
你是否遇到过这样的情况:打开一个图片很多的网页时,半天都加载不出来,进度条慢悠悠地爬?这是因为传统网页会一股脑加载所有图片,不管你看不看得到。而懒加载技术就像一个聪明的管家,只在你需要的时候才把图片呈现出来。今天我们就来聊聊这个提升网页性能的小技巧,看看它是如何让网页加载速度飞起来的。
什么是懒加载?为什么需要它?
上面提到的——当我们打开图片密集型网页时,加载半天还在转圈圈的情况,这是因为传统网页会一次性加载所有图片资源,导致:
- 首屏加载缓慢,用户体验差
- 浪费带宽(尤其对移动用户)
- 服务器压力增大
而 懒加载 的核心思想是: 只加载用户当前可见区域的图片 ,滚动到哪里就加载到哪里,从根源上解决上述问题。
接下来让我们看看懒加载的具体实现。
实现原理:从传统方案到现代API
一、传统方案:监听滚动事件(基于scroll)
传统懒加载通过监听 scroll 事件,在用户滚动页面时动态检查图片是否进入视口,从而触发加载。核心思路是: 用占位符图片代替真实图片地址,通过自定义属性存储真实URL,在图片进入视口时替换src属性 。
<!-- HTML结构 -->
<img class="image-item"
lazyload="true"
src="占位图地址"
data-original="真实图片地址"/>
<script>
// 1. 获取视窗高度
const viewHeight = document.documentElement.clientHeight;
// 2. 选择所有需要懒加载的图片
const eles = document.querySelectorAll("img[data-original][lazyload]");
// 3. 核心懒加载逻辑
const lazyLoad = function () {
Array.prototype.forEach.call(eles, function (item) {
// 跳过已加载的图片
if (item.dataset.original === "") return;
// 获取元素位置信息
const rect = item.getBoundingClientRect();
// 4. 判断是否进入视口
if (rect.top < viewHeight && rect.bottom > 0) {
// 5. 预加载图片
(function () {
var img = new Image();
img.src = item.dataset.original;
img.onload = function () {
// 加载完成后替换src
item.src = item.dataset.original;
// 移除标记属性
item.removeAttribute("lazyload");
};
})();
}
});
};
// 6. 绑定事件监听
window.addEventListener("scroll", lazyLoad);
document.addEventListener("DOMContentLoaded", lazyLoad);
</script>
关键技术点解析
-
占位符与真实地址分离
- src 属性使用空白图片或loading动画(如 blank.png )
- data-original 存储真实图片URL,避免浏览器自动加载
-
视口判断逻辑
// 元素顶部进入视口且底部未完全离开视口
if (rect.top < viewHeight && rect.bottom > 0)
通过 getBoundingClientRect() 获取元素相对于视口的位置, rect.top 是元素顶部到视口顶部的距离
-
图片预加载优化 使用 new Image() 创建图片对象进行预加载,在 onload 回调中才替换 src ,避免加载过程中出现空白
-
事件触发时机
- DOMContentLoaded :页面初始加载时检查首屏图片
- scroll :滚动过程中实时检查
二、现代方案:IntersectionObserver API(推荐)
IntersectionObserver是浏览器提供的异步观察元素交叉状态的API,通过监听目标元素与视口的交叉情况自动触发回调,无需手动监听scroll事件。核心优势在于 异步执行 和 自动性能优化 ,解决了传统方案的性能瓶颈。
<ul id="list">
<li class="list-item">
<img
src=""
class="list-item-img"
lazyload="true"
data-original="https://img.36krcdn.com/hsossms/20250313/v2_15ad8ef9eca34830b4a2e081bbc7f57a@000000_oswg172644oswg1536oswg722_img_000?x-oss-process=image/resize,m_mfit,w_960,h_400,limit_0/crop,w_960,h_400,g_center"
/>
</li>
<!-- 更多图片... -->
</ul>
<script>
function addObserver() {
// 1. 选择所有需要懒加载的图片
const eles = document.querySelectorAll('img[data-original][lazyload]');
// 2. 创建IntersectionObserver实例
const observer = new IntersectionObserver(function(changes) {
// 3. 交叉状态变化时触发的回调
changes.forEach(function(element) {
// 4. 判断元素是否进入视口
if (element.intersectionRatio > 0 && element.intersectionRatio <= 1) {
// 5. 预加载图片
const img = new Image();
img.src = element.target.dataset.original;
img.onload = function() {
// 加载完成后替换src
element.target.src = img.src;
};
// 6. 停止观察已加载的图片
observer.unobserve(element.target);
}
});
});
// 7. 开始观察所有目标元素
eles.forEach((item) => observer.observe(item));
}
// 页面加载后初始化
addObserver();
</script>
关键技术点解析
- IntersectionObserver构造函数
const observer = new IntersectionObserver(callback(){});
// callback 是元素交叉状态变化时的回调函数
- 交叉状态判断
if (element.intersectionRatio > 0 && element.intersectionRatio <= 1)
- intersectionRatio :元素可见比例(0~1),>0表示进入视口
- 相比传统方案的 getBoundingClientRect() ,此判断完全由浏览器优化执行
- 自动停止观察
observer.unobserve(element.target): 加载完成后停止观察,避免重复触发,优化性能 - 批量观察机制
eles.forEach((item) => observer.observe(item)):一次性对所有图片元素进行观察,代码更简洁
两种方案对比表
| 特性 | 传统scroll方案 | IntersectionObserver方案 |
|---|---|---|
| 兼容性 | 所有浏览器 | IE不支持,现代浏览器支持 |
| 性能 | 较差(需手动优化) | 优秀(浏览器原生优化) |
| 代码复杂度 | 较高(需处理位置计算) | 低(API封装完善) |
| 触发频率 | 极高(需节流) | 按需触发(只在可见性变化时) |
| 额外依赖 | 无 | 旧浏览器需polyfill |
实际项目建议:
新项目直接使用 IntersectionObserver ,简洁高效。
如果需要兼容旧浏览器 :主方案的话仍然是使用 IntersectionObserver,降级方案——传统scroll监听(通过特性检测实现)不过传统方案在 性能优化方面 需配合 节流
总结
图片懒加载是前端性能优化的基础手段,从早期的scroll监听发展到现在的IntersectionObserver API,实现方式越来越优雅。建议在实际项目中优先采用IntersectionObserver方案,兼顾性能和开发效率。