JavaScript图片懒加载实战:基于IntersectionObserver API的完整实现
在现代前端开发中,图片懒加载是提升页面加载速度和用户体验的常用手段。传统的懒加载方案多依赖 scroll 事件监听或第三方库,性能开销较大且实现复杂。本文基于原生 IntersectionObserver API 实现一套完整、高性能的图片懒加载方案,无需引入任何外部依赖,并附带可运行的完整代码。// 配置项
const TOTAL_ITEMS = 99;
const DEFAULT_IMG = 'https://pica.zhimg.com/v2-f052aa50ca65df4bad1c3b7e4084d00e_1440w.jpg';
const IMG_URL_TEMPLATE = (index) => `https://picsum.photos/400/600?r=${index}`;
const cardList = document.querySelector('.card-list');
// 生成图片卡片 - 使用 DocumentFragment 批量操作 DOM
function generateItems() {
const fragment = document.createDocumentFragment();
for (let i = 0; i < TOTAL_ITEMS; i++) {
const div = document.createElement('div');
div.classList.add('item');
const img = document.createElement('img');
img.src = DEFAULT_IMG;
img.dataset.src = IMG_URL_TEMPLATE(i);
img.alt = `Image ${i + 1}`;
div.appendChild(img);
fragment.appendChild(div);
}
cardList.appendChild(fragment);
}
// 初始化 IntersectionObserver
function initLazyLoad() {
const observer = new IntersectionObserver(
(entries, observer) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
});
},
{
threshold: 0.01,
}
);
document.querySelectorAll('img').forEach((img) => observer.observe(img));
}
generateItems();
initLazyLoad();
技术选型说明
IntersectionObserver 是浏览器原生 API,专门用于异步观察目标元素与根元素的交叉状态。相比 scroll 事件+getBoundingClientRect 的传统做法,IntersectionObserver 具有以下优势:
- 自动优化回调触发时机,减少主线程负担
- 支持阈值(threshold)精确控制加载时机
- 内置 unobserve 方法,加载完成后立即停止观察,避免无效计算
- Chrome 51+、Firefox 55+、Safari 12.1+、Edge 79+ 均已支持
完整代码实现
HTML 结构:只需一个容器 div.card-list,通过 JavaScript 动态生成图片卡片。
CSS 样式:采用 CSS Grid 自适应布局,每张卡片固定高度 497px,图片占满卡片并带悬停缩放效果(1.5 倍)。同时自定义了滚动条样式,提升视觉体验。
核心逻辑解析
1. 图片卡片生成
使用 DocumentFragment 暂存所有卡片元素,最后一次性 append 到父容器。这避免了循环中逐次操作 DOM 导致的多次重绘重排,在大批量生成(如99张)时性能提升明显。
初始 img.src 指向一张较小尺寸的占位图,真实图片地址存储在 data-src 自定义属性中,直到图片进入视口才替换。
动态图片地址通过模板函数生成,每次请求附带不同的随机参数(?r=index),避免浏览器缓存导致的旧图问题。
2. IntersectionObserver 配置
threshold: 0.01
表示目标元素进入视口 1% 时触发回调。这样设置可以在图片即将完全展现前提前加载,避免用户滚动到位置后出现白屏闪烁。
入口回调中,先判断 entry.isIntersecting 为 true 才执行加载,否则直接跳过。加载完成后立即调用 observer.unobserve(img),确保同一图片不会重复触发。
3. API 配置项速查
| 配置项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| threshold | number | 0 | 交叉比例阈值,例如 0.5 表示元素50%可见时触发 |
| root | Element | null | 观察的根容器,默认为浏览器视口 |
| rootMargin | string | '0px' | 根元素的外边距,可设为 '200px' 实现提前加载 |
性能优化细节
- DocumentFragment 批量插入 DOM
- 加载完成后立即解除观察(unobserve)
- 占位图使用相对较小的图片(例如本示例中的 400x600 缩略图或更小的占位图)
- 可选结合原生 loading="lazy" 属性作为降级方案,但 IntersectionObserver 更精细可控
兼容性及扩展建议
- 如需兼容 IE 或旧版浏览器,可引入 intersection-observer polyfill
- 图片加载失败时,可监听 img.onerror 事件替换为错误占位图,防止裂图
- 实际项目中建议为 data-src 中的图片地址添加 CDN 或其他可靠源
- 若页面上存在大量图片,可考虑将 threshold 设为 0 或使用 rootMargin 提前加载,以优化滚动体验
本方案已在多个生产项目中验证,稳定可靠。开发者只需根据实际需求调整 TOTAL_ITEMS 和图片地址模板即可快速接入。
Re: JavaScript图片懒加载实战:基于IntersectionObserver API的完整实现
感谢分享,这个实现很完整。我特别欣赏你使用 DocumentFragment 批量操作 DOM 和加载后立即调用 unobserve 的思路,确实能避免不必要的性能开销。关于 threshold 设为 0.01,在实际项目中如果图片在视口边缘快速滚动,是否会因为触发回调轻微延迟而出现短暂空白?我一般会用 rootMargin: '200px' 来提前加载,不知道你怎么看?另外图片加载失败的处理如果能补充一下,比如监听 error 事件替换默认图,就更稳妥了。总之,很实用的方案。Re: JavaScript图片懒加载实战:基于IntersectionObserver API的完整实现
感谢楼主分享,写得非常详细!IntersectionObserver 确实是现代懒加载的利器,代码里用 DocumentFragment 批量生成和加载后立即 unobserve 这两个细节很到位,避免了常见的性能坑。我正好在重构一个图片瀑布流页面,按你的思路改掉之前的 scroll 监听,应该能省不少计算资源。另外想请教下,对于移动端那种快速滚动的情况,threshold 设 0.01 会不会偶尔闪一下?还是说配合 rootMargin 提前加载效果更好?再次感谢,收藏了!Re: JavaScript图片懒加载实战:基于IntersectionObserver API的完整实现
楼主的实现非常清晰完整,从技术选型到代码注释都很有参考价值。用原生IntersectionObserver替代scroll事件确实能显著提升性能,DocumentFragment批量生成卡片和加载后立即unobserve的设计也很到位,减少了不必要的重绘和重复检测。threshold设为0.01这个细节很不错,平衡了提前加载和节省流量。想问楼主在实际项目里,如果遇到图片加载失败的情况,除了监听onerror外,还有没有其他推荐的降级或重试策略?另外,对于列表滚动中大量observer同时实例化,是否建议通过rootMargin先控制一下监听范围来进一步优化?
页:
[1]