HTML图片懒加载实现指南
在网页开发中,如果页面包含大量图片,一次性加载所有图片会消耗大量带宽,延长页面首屏渲染时间,影响用户体验。图片懒加载就是一种优化方案,它的核心思路是:只有当图片进入浏览器的可视区域时,才去加载对应的图片资源,从而减少不必要的网络请求,提升页面加载速度。
图片懒加载的核心实现思路
实现图片懒加载主要依赖以下几个关键点:
- 给图片标签设置自定义属性(比如
data-src)存放真实的图片地址,src属性先设置为占位图或者空值,避免提前发起图片请求。 - 监听浏览器的滚动事件、窗口大小改变事件,或者利用浏览器提供的
IntersectionObserverAPI,判断图片是否已经进入可视区域。 - 当图片进入可视区域时,把自定义属性里的真实图片地址赋值给
src属性,触发图片加载,同时可以移除对应的监听逻辑,避免重复判断。
基于IntersectionObserver的实现方案(推荐)
IntersectionObserver是浏览器原生提供的API,用于异步观察目标元素与祖先元素或顶级文档视口的交叉状态,不需要手动计算元素位置,性能比监听滚动事件更好,是目前实现懒加载的首选方案。
下面是完整的实现代码示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片懒加载示例</title>
<style>
/* 占位样式,模拟图片未加载时的占位区域 */
.lazy-img {
width: 300px;
height: 200px;
background-color: #f0f0f0;
margin: 20px auto;
display: block;
}
/* 图片加载完成后移除占位背景 */
.lazy-img.loaded {
background-color: transparent;
}
</style>
</head>
<body>
<h3>向下滚动查看懒加载效果</h3>
<p>页面下方有5张需要懒加载的图片,滚动到对应位置才会加载。</p>
<!-- 占位内容,撑开页面高度,方便演示滚动效果 -->
<div style="height: 800px;"></div>
<!-- 懒加载图片,真实地址存在data-src中,src先放占位图 -->
<img class="lazy-img"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
data-src="https://cdn.ipipp.com/demo/img1.jpg"
alt="示例图片1">
<img class="lazy-img"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
data-src="https://cdn.ipipp.com/demo/img2.jpg"
alt="示例图片2">
<img class="lazy-img"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
data-src="https://cdn.ipipp.com/demo/img3.jpg"
alt="示例图片3">
<img class="lazy-img"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
data-src="https://cdn.ipipp.com/demo/img4.jpg"
alt="示例图片4">
<img class="lazy-img"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
data-src="https://cdn.ipipp.com/demo/img5.jpg"
alt="示例图片5">
<div style="height: 800px;"></div>
<script>
// 获取所有需要懒加载的图片元素
const lazyImages = document.querySelectorAll('.lazy-img');
// 创建IntersectionObserver实例,配置回调和可选参数
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
// 如果目标元素进入可视区域
if (entry.isIntersecting) {
const img = entry.target;
// 将data-src里的真实地址赋值给src,触发图片加载
const realSrc = img.getAttribute('data-src');
if (realSrc) {
img.src = realSrc;
// 加载完成后移除占位样式
img.onload = () => {
img.classList.add('loaded');
};
}
// 图片开始加载后,不再观察该元素,避免重复触发
observer.unobserve(img);
}
});
}, {
// 提前100px触发加载,让图片有足够时间加载,提升体验
rootMargin: '100px'
});
// 遍历所有懒加载图片,开始观察
lazyImages.forEach(img => {
observer.observe(img);
});
</script>
</body>
</html>上面的代码中,我们首先给所有需要懒加载的图片添加了lazy-img类,src属性使用了一个1x1像素的透明占位图,真实图片地址存在data-src自定义属性中。然后创建IntersectionObserver实例,设置当图片进入可视区域前100px时就触发回调,在回调中把data-src的值赋给src,同时停止对该图片的观察,避免重复处理。
兼容旧浏览器的滚动监听实现方案
如果项目需要兼容不支持IntersectionObserver的旧浏览器(比如IE系列),可以使用监听滚动事件的方案,通过手动计算元素位置判断是否进入可视区域。
下面是滚动监听方案的代码示例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>滚动监听懒加载示例</title>
<style>
.lazy-img {
width: 300px;
height: 200px;
background-color: #f0f0f0;
margin: 20px auto;
display: block;
}
.lazy-img.loaded {
background-color: transparent;
}
</style>
</head>
<body>
<h3>向下滚动查看懒加载效果</h3>
<p>兼容旧浏览器的懒加载实现,通过监听滚动事件判断图片位置。</p>
<div style="height: 800px;"></div>
<img class="lazy-img"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
data-src="https://cdn.ipipp.com/demo/img1.jpg"
alt="示例图片1">
<img class="lazy-img"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
data-src="https://cdn.ipipp.com/demo/img2.jpg"
alt="示例图片2">
<img class="lazy-img"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
data-src="https://cdn.ipipp.com/demo/img3.jpg"
alt="示例图片3">
<div style="height: 800px;"></div>
<script>
// 获取所有需要懒加载的图片
const lazyImages = document.querySelectorAll('.lazy-img');
// 节流变量,避免滚动事件触发过于频繁
let isThrottle = false;
// 判断图片是否进入可视区域的函数
function isInViewport(element) {
const rect = element.getBoundingClientRect();
// 元素的底部位置大于0,且顶部位置小于窗口高度,说明进入可视区域
return rect.bottom > 0 && rect.top < window.innerHeight;
}
// 加载进入可视区域的图片
function loadLazyImages() {
// 遍历所有未加载的图片
lazyImages.forEach((img, index) => {
// 如果图片已经加载过,跳过
if (img.classList.contains('loaded')) return;
// 判断图片是否进入可视区域
if (isInViewport(img)) {
const realSrc = img.getAttribute('data-src');
if (realSrc) {
img.src = realSrc;
img.onload = () => {
img.classList.add('loaded');
};
}
}
});
// 检查是否所有图片都已加载,是的话移除滚动监听
const unloadedImages = document.querySelectorAll('.lazy-img:not(.loaded)');
if (unloadedImages.length === 0) {
window.removeEventListener('scroll', handleScroll);
window.removeEventListener('resize', handleScroll);
}
}
// 滚动事件处理函数,使用节流优化性能
function handleScroll() {
if (isThrottle) return;
isThrottle = true;
// 使用requestAnimationFrame在下一帧执行,避免频繁计算
requestAnimationFrame(() => {
loadLazyImages();
isThrottle = false;
});
}
// 初始执行一次,加载首屏已经在可视区域的图片
loadLazyImages();
// 监听滚动、窗口大小改变事件
window.addEventListener('scroll', handleScroll);
window.addEventListener('resize', handleScroll);
</script>
</body>
</html>这个方案中,我们通过getBoundingClientRect()方法获取图片相对于视口的位置,判断是否在可视区域内。为了避免滚动事件触发过于频繁导致性能问题,使用了节流逻辑,结合requestAnimationFrame控制执行频率。当所有图片都加载完成后,会移除滚动和 resize 事件监听,减少不必要的性能消耗。
两种方案的对比与选择
| 对比维度 | IntersectionObserver方案 | 滚动监听方案 |
|---|---|---|
| 性能表现 | 原生API异步执行,性能更好,不会阻塞主线程 | 需要手动计算位置,频繁触发时可能消耗更多性能,需要额外做节流优化 |
| 浏览器兼容性 | 支持现代浏览器,IE全系列不支持 | 兼容性更好,几乎所有浏览器都支持 |
| 实现复杂度 | 代码逻辑简单,不需要手动计算位置 | 逻辑相对复杂,需要处理位置计算、事件节流、监听移除等 |
| 适用场景 | 面向现代浏览器的项目,优先选择该方案 | 需要兼容旧浏览器(如IE)的项目使用 |
注意事项
- 占位图建议使用极小的透明图或者纯色背景,避免占位图本身过大影响加载速度。
- 如果页面是动态插入图片的,需要给新插入的图片也添加对应的懒加载观察逻辑,避免动态图片无法触发懒加载。
- 使用
IntersectionObserver时,可以通过rootMargin参数设置提前加载的距离,比如设置为'200px',可以让图片在进入可视区域前200px就开始加载,避免用户滚动到图片位置时还需要等待加载。 - 如果图片加载失败,可以添加错误处理的逻辑,比如显示加载失败的占位提示,提升用户体验。