JavaScript 图片懒加载实现指南
图片懒加载是一种常用的前端性能优化技术,其核心思想是仅当图片即将进入用户可视区域时才开始加载,而不是一次性加载页面中的所有图片。这有助于显著减少初始页面加载带宽、降低服务器压力,并提升用户体验。在 JavaScript 中,实现图片懒加载主要有两种主流方式:基于传统的 scroll 事件监听与元素位置计算,以及基于现代浏览器提供的 Intersection Observer API。
传统方法:使用 scroll 事件
传统的懒加载实现依赖于监听页面的 scroll 和 resize 事件,并实时计算图片元素相对于视口的位置。通常,我们会将图片的真实地址存储在 data-src 自定义属性中,而不是直接写在 src 属性里。当图片的位置距离顶部小于视口高度与滚动偏移量之和时,才将 data-src 的值赋给 src 并触发加载。
<!DOCTYPE html>
<html>
<head>
<style>
img {
display: block;
width: 400px;
height: 400px;
margin-bottom: 800px;
background: #eee; /* 占位背景色 */
}
</style>
</head>
<body>
<img class="lazy-load" data-src="https://ippipp.com/400x400?text=Image+1" alt="懒加载图片1">
<img class="lazy-load" data-src="https://ippipp.com/400x400?text=Image+2" alt="懒加载图片2">
<img class="lazy-load" data-src="https://ippipp.com/400x400?text=Image+3" alt="懒加载图片3">
<script>
// 获取所有需要懒加载的图片
const lazyImages = document.querySelectorAll('.lazy-load');
// 判断元素是否进入视口
function isElementInViewport(el) {
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
// 加载可见的图片
function lazyLoad() {
lazyImages.forEach(img => {
// 如果图片已加载,跳过
if (img.classList.contains('loaded')) {
return;
}
// 如果图片进入视口,则加载
if (isElementInViewport(img)) {
img.src = img.dataset.src;
img.classList.add('loaded');
img.classList.remove('lazy-load');
}
});
// 如果所有图片都已加载,移除事件监听以节省性能
if (document.querySelectorAll('.lazy-load').length === 0) {
document.removeEventListener('scroll', lazyLoad);
window.removeEventListener('resize', lazyLoad);
window.removeEventListener('orientationchange', lazyLoad);
}
}
// 初始检查并绑定事件
lazyLoad();
document.addEventListener('scroll', lazyLoad);
window.addEventListener('resize', lazyLoad);
window.addEventListener('orientationchange', lazyLoad);
</script>
</body>
</html>上述代码展示了 scroll 事件方法的完整实现。我们首先定义了一个 isElementInViewport 函数,它通过 getBoundingClientRect() 获取元素的大小及其相对于视口的位置,并判断元素是否完全可见。在 lazyLoad 函数中,我们遍历所有待加载的图片,检查其是否进入视口。一旦进入,则将其 data-src 属性值赋给 src,并通过添加 loaded 类标记已加载状态。最后,我们通过事件监听持续跟踪滚动、窗口尺寸变化以及设备方向变化。
传统方法虽然兼容性极佳,但存在明显的性能问题。每次滚动或调整窗口大小时,都会触发大量的位置计算,如果页面中存在大量图片,频繁的 getBoundingClientRect 调用容易导致主线程阻塞,引发页面卡顿。此外,scroll 事件的触发频率很高,通常需要配合 requestAnimationFrame 或节流函数来优化。
现代方法:使用 Intersection Observer API
Intersection Observer API 是浏览器提供的一种更高效、更简洁的解决方案。它允许我们以异步方式观察目标元素与其祖先元素或顶级文档视口的交叉状态。当被观察的元素进入或离开视口时,浏览器会自动触发回调函数,无需我们手动计算位置或监听 scroll 事件。
<!DOCTYPE html>
<html>
<head>
<style>
img {
display: block;
width: 400px;
height: 400px;
margin-bottom: 800px;
background: #f0f0f0;
}
</style>
</head>
<body>
<img class="lazy-img" data-src="https://ippipp.com/400x400?text=Intersection+1" alt="现代懒加载1">
<img class="lazy-img" data-src="https://ippipp.com/400x400?text=Intersection+2" alt="现代懒加载2">
<img class="lazy-img" data-src="https://ippipp.com/400x400?text=Intersection+3" alt="现代懒加载3">
<script>
document.addEventListener('DOMContentLoaded', function() {
const lazyImages = document.querySelectorAll('.lazy-img');
// 创建 Intersection Observer 实例
const observer = new IntersectionObserver(function(entries, observerInstance) {
entries.forEach(entry => {
// 如果目标元素与视口相交
if (entry.isIntersecting) {
const img = entry.target;
// 将 data-src 的值赋给 src,触发图片加载
img.src = img.dataset.src;
// 图片加载完成后移除类名,并停止观察该元素
img.classList.remove('lazy-img');
img.classList.add('loaded');
observerInstance.unobserve(img);
}
});
}, {
// rootMargin: '0px 0px 50px 0px', // 可选的偏移量,提前加载
// threshold: 0.01 // 可选的交叉比例阈值
});
// 开始观察每张图片
lazyImages.forEach(img => {
observer.observe(img);
});
});
</script>
</body>
</html>在这个 Intersection Observer 实现的示例中,我们首先在 DOMContentLoaded 事件中获取所有待加载的图片。接着,创建一个 IntersectionObserver 实例,并传入一个回调函数。回调函数接收两个参数:一个 entries 数组和观察器本身的引用。在回调内部,我们遍历每一个 entry,检查其 isIntersecting 属性。如果该属性为 true,说明图片至少有一部分已经进入视口。此时,我们将 data-src 赋给 src,更新 CSS 类名,并通过 unobserve 方法停止观察该元素,避免不必要的性能消耗。
此方法相较于传统方式有显著优势:它由浏览器底层实现,异步执行,不会阻塞主线程;无需手动监听 scroll 事件,大幅减少了事件处理器的数量;并且提供了 rootMargin 和 threshold 等配置项,允许开发者灵活控制“预加载”的触发时机。例如,设置 rootMargin: '100px 0px',可以使得图片在距离视口边缘还有 100 像素时就提前加载。
总结
综合来看,Intersection Observer API 是现代浏览器中实现图片懒加载的推荐方案。它在性能、代码简洁性和可维护性方面均优于传统的 scroll 事件方法。对于需要支持老旧浏览器的项目,可以继续使用 scroll 事件加节流处理的方案,或者引入 polyfill 来填补 Intersection Observer 的兼容性缺口。无论采用哪种方式,核心思想都是相同的:延迟加载非视口内的图片,只在需要时才发送网络请求,从而达到优化性能、节省流量的目的。