前言
在当今互联网时代,网页性能优化已经成为前端开发的重中之重。特别是对于图片密集型的网站(如电商平台、社交媒体等),图片加载往往是影响页面性能的关键因素。今天我们就来深入探讨一下图片懒加载技术,从传统的滚动监听实现到现代的 IntersectionObserver API,让你彻底掌握这项重要的性能优化技术。
🤔 为什么需要图片懒加载?
性能痛点分析
想象一下,你正在开发一个电商网站,首页需要展示50+张商品图片。如果采用传统的加载方式:
<!-- 传统方式:所有图片同时加载 -->
<img src="https://example.com/product1.jpg" />
<img src="https://example.com/product2.jpg" />
<img src="https://example.com/product3.jpg" />
<!-- ... 更多图片 -->
会带来以下问题:
- 首屏加载缓慢:浏览器需要同时下载所有图片
- 网络带宽浪费:用户可能只查看首屏内容,却下载了所有图片
- 用户体验差:页面长时间白屏,用户容易流失
懒加载的核心思想
只加载用户需要看到的图片,这就是懒加载的核心理念:
- ✅ 首屏可见的图片立即加载
- ✅ 非可见区域的图片延迟加载
- ✅ 滚动到对应位置时再加载对应图片
🛠️ 传统实现方案:基于滚动监听
基本实现思路
- 占位图设置:使用小尺寸占位图或透明图片
- 数据属性存储:将真实图片地址存储在
data-original属性中 - 滚动监听:监听
scroll事件,判断图片是否进入可视区域 - 动态替换:符合条件时替换
src属性
完整代码实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片懒加载 - 传统方案</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
background-color: #000;
}
.image-item {
width: 500px;
height: 500px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<!-- 占位图 + 数据属性存储真实地址 -->
<img class="image-item"
lazyload="true"
src="https://static.360buyimg.com/item/main/1.0.12/css/i/loading.gif"
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" />
<!-- 更多图片... -->
<script>
const viewHeight = document.documentElement.clientHeight;
const eles = document.querySelectorAll('img[data-original][lazyload]');
const lazyload = function() {
Array.prototype.forEach.call(eles, function(item, index) {
// 没有数据源则跳过
if (item.dataset.original === "") return;
// 获取元素在视口中的位置信息
const rect = item.getBoundingClientRect();
// 判断是否在可视区域内
if (rect.bottom >= 0 && rect.top < viewHeight) {
// 创建新的Image对象进行预加载
(function() {
var img = new Image();
img.src = item.dataset.original;
img.onload = function() {
// 加载完成后替换src
item.src = item.dataset.original;
// 清理属性,避免重复处理
item.removeAttribute('data-original');
item.removeAttribute('lazyload');
}
})()
}
})
}
// 绑定滚动事件和页面加载事件
window.addEventListener('scroll', lazyload);
document.addEventListener('DOMContentLoaded', lazyload);
</script>
</body>
</html>
核心技术点解析
1. 视口检测算法
const rect = item.getBoundingClientRect();
if (rect.bottom >= 0 && rect.top < viewHeight) {
// 图片进入可视区域
}
getBoundingClientRect()返回元素相对于视口的位置信息rect.bottom >= 0:元素底部在视口顶部以下rect.top < viewHeight:元素顶部在视口底部以上
2. 图片预加载技术
var img = new Image();
img.src = item.dataset.original;
img.onload = function() {
item.src = item.dataset.original;
}
使用内存中的 Image 对象进行预加载,确保图片完全加载后再显示,避免加载过程中的闪烁。
🎯 现代实现方案:IntersectionObserver API
传统方案的性能问题
传统滚动监听方案存在以下性能问题:
- 高频触发:
scroll事件触发频率极高 - 同步执行:
getBoundingClientRect()会触发浏览器回流 - 主线程阻塞:大量同步计算影响页面流畅度
IntersectionObserver 的优势
- ✅ 异步执行:不阻塞主线程
- ✅ 浏览器优化:由浏览器底层优化交叉检测
- ✅ 精确控制:可设置触发阈值和根元素
完整代码实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片懒加载 - IntersectionObserver</title>
<style>
.list-item {
height: 400px;
margin: 5px;
list-style: none;
}
.list-item-img {
height: 100%;
}
</style>
</head>
<body>
<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() {
// 获取所有需要懒加载的图片
const eles = document.querySelectorAll('img[data-original][lazyload]');
// 创建交叉观察器
const observer = new IntersectionObserver(function(changes) {
changes.forEach(function(element) {
// 检查交叉比例
if (element.intersectionRatio > 0 && element.intersectionRatio <= 1) {
// 图片进入视口,开始加载
const img = new Image();
img.src = element.target.dataset.original;
img.onload = function() {
element.target.src = img.src;
// 停止观察已加载的图片
observer.unobserve(element.target);
}
}
})
});
// 开始观察所有图片元素
eles.forEach((item) => {
observer.observe(item);
});
}
// 页面加载完成后初始化观察器
addObserver();
</script>
</body>
</html>
IntersectionObserver 深度解析
1. 观察器模式的应用
const observer = new IntersectionObserver(callback, options);
- 观察者:IntersectionObserver 实例
- 被观察者:需要懒加载的图片元素
- 回调函数:元素可见性变化时的处理逻辑
2. 交叉比例判断
if (element.intersectionRatio > 0 && element.intersectionRatio <= 1) {
// 元素部分或完全可见
}
intersectionRatio表示元素在视口中的可见比例- 值域为
[0, 1],0表示完全不可见,1表示完全可见
3. 性能优化细节
img.onload = function() {
element.target.src = img.src;
observer.unobserve(element.target); // 🔥 重要:停止观察已加载的元素
}
📊 性能对比与最佳实践
性能对比
| 方案 | scroll 事件 | IntersectionObserver |
|---|---|---|
| 执行方式 | 同步 | 异步 |
| 触发频率 | 极高 | 按需 |
| 浏览器优化 | 有限 | 深度优化 |
| 主线程影响 | 较大 | 极小 |
| 兼容性 | 优秀 | 现代浏览器 |
最佳实践建议
1. 占位图策略
// 方案一:使用小尺寸loading图
src="https://static.360buyimg.com/item/main/1.0.12/css/i/loading.gif"
// 方案二:使用透明占位图
src="https://misc.360buyimg.com/mtd/pc/common/img/blank.png"
// 方案三:使用CSS背景色占位
.image-placeholder {
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
}
2. 预加载优化
// 预加载机制确保图片完全加载后再显示
const img = new Image();
img.src = element.target.dataset.original;
img.onload = function() {
element.target.src = img.src;
// 可选:添加淡入动画
element.target.style.opacity = '0';
element.target.style.transition = 'opacity 0.3s';
setTimeout(() => element.target.style.opacity = '1', 10);
}
3. 错误处理
img.onerror = function() {
// 加载失败时显示默认图片
element.target.src = '/images/default-error.png';
observer.unobserve(element.target);
}
🔧 进阶优化技巧
1. 防抖与节流
对于传统滚动方案,建议添加防抖处理:
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
window.addEventListener('scroll', debounce(lazyload, 100));
2. 可见性阈值设置
const observer = new IntersectionObserver(callback, {
rootMargin: '50px', // 提前50px开始加载
threshold: 0.1 // 元素10%可见时触发
});
3. 兼容性处理
function createLazyLoader() {
if ('IntersectionObserver' in window) {
return createIntersectionObserverLoader();
} else {
return createScrollLoader();
}
}
📱 移动端适配
触摸滚动优化
// 监听触摸事件
document.addEventListener('touchmove', debounce(lazyload, 100));
document.addEventListener('touchend', lazyload);
响应式图片处理
<img data-original-mobile="mobile.jpg"
data-original-desktop="desktop.jpg"
lazyload="true" />
🎉 总结
图片懒加载技术是现代web开发中不可或缺的性能优化手段。通过本文的详细讲解,我们了解了:
- 传统滚动监听方案:实现简单,兼容性好,但性能有限
- IntersectionObserver方案:性能优秀,现代浏览器首选
- 最佳实践:占位图策略、预加载机制、错误处理等
选择合适的方案需要根据项目需求和目标用户群体来决定。对于现代项目,建议优先使用 IntersectionObserver API,并提供传统方案作为降级方案。
记住一个核心原则:用户体验 > 技术实现,性能优化的最终目标是提升用户体验!