前端性能优化:图片懒加载
一、懒加载的作用
- 提高页面加载速度:通过避免一次性加载所有图片,减少初次加载的资源量,加快页面渲染速度。
- 节省带宽:对于未进入可视区域的图片,不发送网络请求,减少不必要的数据传输,对移动端用户特别友好。
- 提升用户体验:用户可以更快地看到页面内容,而不是等待所有图片加载完成。
- 降低服务器压力:分批加载图片,分散服务器的请求压力,优化资源分配。
这项技术特别适用于:
-
图片密集型网站(电商、图库、社交媒体)
-
长页面或无限滚动页面
-
移动端网页和应用
通过智能的资源加载策略,图片懒加载在几乎不影响功能完整性的前提下,为用户带来了质的性能提升,成为现代Web开发中不可或缺的优化手段。
二、实现方式
原理:先加载一张默认图,页面显示图片的时候先加载默认图,等图片加载完成,再用data-src替换默认图片。
图片懒加载(Lazy Loading)通常有三种主流实现方式,IntersectionObserver 是其中性能和代码简洁性最优的方案。以下是三种方式的对比和示例:
2.1. 原生 loading="lazy"(最简单)
原理:HTML 原生属性,浏览器自动处理懒加载。
优点:无需 JavaScript,兼容现代浏览器。
缺点:旧浏览器不支持,无法精细控制(如回调)。
<img src="https://cdn.someacg.top/graph/thumb/128819907_p0_scale.jpg" loading="lazy">
loading="lazy" 是 HTML 原生懒加载,但它 只作用于 src 属性,不会自动替换 data-src:
使用问题它缺少以下关键点:
- 没有占位图到真实图的过渡效果
- 未处理加载失败的情况
- 无加载状态提示(如旋转动画)
2.2 IntersectionObserver(推荐)
IntersectionObserver 是浏览器提供的一个 API,用于异步监听目标元素与其祖先元素或视口(viewport)的交叉状态(即是否进入/离开可视区域)。当图片进入可视区域后会被IntersectionObserver 监听到,然后替换图片.
实现流程如下:
-
初始化占位阶段
-
在
<img>标签上同时设置src(占位图)和data-src(真实图片地址)属性 -
页面首次加载时仅请求轻量级的占位图片
-
-
可视区域检测阶段
-
通过
IntersectionObserver建立观察器 -
实时监测图片元素与视口的交叉状态
-
-
预加载处理阶段
-
当图片进入预定可视区域时(可配置触发阈值)
-
创建
new Image()实例进行图片预加载 -
通过预加载实例的
onload事件确保图片完整加载
-
-
无缝替换阶段
-
待图片资源完全加载后
-
将原
<img>标签的src属性替换为真实图片地址 -
实现从占位图到真实图片的无缝过渡
-
实现代码
<img src="./img/1.gif" class="lazy-img" data-src="https://cdn.someacg.top/graph/thumb/128819907_p0_scale.jpg">
<script>
const lazyImages = document.querySelectorAll('.lazy-img');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
const realSrc = img.dataset.src;
// 先加载真实图片,等加载完成再替换
const tempImg = new Image();
tempImg.src = realSrc;
tempImg.onload = () => {
img.src = realSrc; // 替换为真实图片
};
observer.unobserve(img);
}
});
}, {
rootMargin: '200px',
threshold: 0.5 // 默认为0,表示只要图片进入可视区即可触发回调 。0.5 表示至少一半在可视区才触发回调
});
lazyImages.forEach(img => {
observer.observe(img);
});
</script>
2.3. 滚动事件 + getBoundingClientRect()(传统方式)
这种传统实现方案的核心是通过监听滚动事件结合元素位置计算来实现懒加载,其工作原理可分为三个关键环节:
1. 元素位置检测机制
-
使用
getBoundingClientRect()方法获取元素相对于视口的精确位置信息 -
通过
top、bottom等属性值计算元素与视口的空间关系 -
结合
window.innerHeight获取当前视口高度进行位置比对
2. 滚动事件监听系统
-
通过
window.addEventListener('scroll')建立滚动监听 -
采用函数节流(throttle)技术优化性能(推荐16ms/次的执行间隔)
-
兼容resize事件处理,确保浏览器窗口大小变化时能重新计算
3. 动态加载触发逻辑
-
当检测到元素进入预加载区域(通常为视口下方200-500px)
-
替换data-src为src属性触发图片加载
-
加载完成后移除事件监听避免重复计算
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
img {
width: 100%;
}
</style>
</head>
<body>
<img src="./img/1.gif" class="lazy-img" data-src="https://cdn.someacg.top/graph/thumb/128819907_p0_scale.jpg">
<script>
document.addEventListener('DOMContentLoaded', function () {
const lazyImages = document.querySelectorAll('.lazy-img');
// 立即执行一次检查
lazyLoad()
// 然后添加你的滚动监听
window.addEventListener('scroll', debounce(() => {
lazyLoad()
}, 100));
function lazyLoad() {
lazyImages.forEach(img => {
const rect = img.getBoundingClientRect();
if (rect.top < window.innerHeight && rect.bottom > 0) {
const realSrc = img.dataset.src;
img.src = realSrc;
}
});
}
});
//防抖
function debounce(fn, delay) {
let timer = null;
return function () {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments);
}, delay);
};
}
</script>
</body>
</html>
优点:兼容性最好。
缺点:性能差(频繁触发重排/重绘),代码复杂。
2.4:滚动事件 + 滚动距离计算(传统旧方式)
这种输入最古老的方案。实现方案的核心是通过监听滚动事件和元素位置计算来实现懒加载
- 监听
scroll事件(或结合throttle节流优化性能)。 - 计算当前滚动距离(
window.scrollY) + 视口高度(window.innerHeight)。 - 判断是否 >= 目标图片距离页面顶部的偏移量(
offsetTop)。
<img data-src="real-image.jpg" alt="图片" class="lazy-img"> <script> function lazyLoad() { const lazyImages = document.querySelectorAll('.lazy-img:not([src])'); const scrollTop = window.scrollY; const viewportHeight = window.innerHeight; lazyImages.forEach(img => { // 计算图片距离文档顶部的距离 const imgOffsetTop = img.offsetTop; // 判断:滚动距离 + 视口高度 >= 图片顶部距离(提前 200px 加载) if (scrollTop + viewportHeight + 200 >= imgOffsetTop) { img.src = img.dataset.src; // 替换为真实地址 } }); } // 监听滚动事件(建议节流) window.addEventListener('scroll', lazyLoad); window.addEventListener('load', lazyLoad); // 初始加载 lazyLoad(); // 首次检查 </script>
四种方式对比
| 方式 | 实现逻辑 | 性能 | 适用场景 |
|---|---|---|---|
| 滚动距离计算 | scrollY + innerHeight > offsetTop |
⭐⭐ | 兼容性要求高的旧项目 |
getBoundingClientRect |
计算元素相对于视口的位置 | ⭐⭐ | 需精确控制触发时机 |
IntersectionObserver |
浏览器原生监听交叉状态 | ⭐⭐⭐⭐⭐ | 现代项目,高性能需求 |
loading="lazy" |
HTML 原生属性 | ⭐⭐⭐⭐⭐ | 简单场景,无需控制逻辑 |
三、最佳实践推荐
- 优先使用
IntersectionObserver(高性能 + 可控性强)。 - 简单场景用
loading="lazy"(零 JS 代码,现代浏览器友好)。 - 旧项目兼容用滚动事件 + 节流(但尽量升级到现代方案)。

浙公网安备 33010602011771号