懒加载如何实现?从原理到实战的完整指南
📖 目录导读
什么是懒加载?为什么需要它?
懒加载(Lazy Load) 是一种延迟加载技术,仅在资源(图片、视频、脚本等)进入用户可视区域时才加载,而非页面初始化时全部加载,它的核心价值在于:
- 提升首屏加载速度:减少初始HTTP请求和带宽占用
- 节省流量:移动端用户无需加载未看到的资源
- 改善LCP指标:Google Core Web Vitals 中,懒加载可显著提升 Largest Contentful Paint
适用场景:长页面、瀑布流、电商列表、新闻资讯等包含大量图片或媒体的页面。
懒加载的核心原理
懒加载的实现基于判断元素是否进入视口(viewport),其逻辑闭环如下:
用户滚动页面 → 检测元素与视口的位置关系 → 若进入视口 → 触发资源加载(如替换data-src为src) → 移除监听
两种主流检测方案:
| 方案 | 原理 | 兼容性 | 性能 |
|---|---|---|---|
| getBoundingClientRect() | 计算元素位置与视口距离 | 全兼容(IE9+) | 需绑定scroll事件,频繁计算 |
| Intersection Observer | 浏览器原生API异步监听 | 现代浏览器(IE不支持) | 性能极优,无主线程阻塞 |
三种主流实现方式详解
纯JavaScript(getBoundingClientRect)
// 传统scroll监听方案
const images = document.querySelectorAll('[data-src]');
function checkVisible(el) {
const rect = el.getBoundingClientRect();
return rect.top < window.innerHeight && rect.bottom > 0;
}
function lazyLoad() {
images.forEach(img => {
if (checkVisible(img) && img.dataset.src) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
}
});
// 优化:若所有图片已加载,移除监听
if (document.querySelectorAll('[data-src]').length === 0) {
window.removeEventListener('scroll', lazyLoad);
}
}
// 节流优化
window.addEventListener('scroll', throttle(lazyLoad, 200));
window.addEventListener('resize', throttle(lazyLoad, 200));
缺点:频繁触发scroll事件,需配合throttle/debounce,仍有可能造成主线程阻塞。
Intersection Observer(推荐)
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img); // 加载后取消观察
}
});
}, {
rootMargin: '0px 0px 200px 0px', // 提前200px预加载
threshold: 0.01
});
document.querySelectorAll('[data-src]').forEach(img => observer.observe(img));
优势:
✅ 异步执行,不阻塞主线程
✅ 自动处理滚动、缩放场景
✅ 支持rootMargin提前预加载(提升用户体验)
✅ 兼容Chrome 51+, Firefox 55+, Safari 12.1+
HTML原生loading属性(最简方案)
<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy" alt="示例"> <script> // 注意:仅支持Chrome 76+,Firefox 75+,Safari 15.4+ // 结合data-src作为降级方案 </script>
注意:原生loading="lazy"无需JS,但无法自定义预加载距离,且对<iframe>支持有限。
实战代码:从零构建懒加载
基础HTML结构(使用data-*属性)
<img class="lazy" data-src="https://example.com/high-res.jpg"
src="/placeholder.png"
alt="产品图"
width="800" height="600">
核心JavaScript(双重降级策略)
( function() {
const lazyImages = document.querySelectorAll('.lazy[data-src]');
// 检测浏览器是否支持Intersection Observer
if ('IntersectionObserver' in window) {
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.onload = () => img.classList.add('loaded');
img.removeAttribute('data-src');
obs.unobserve(img);
}
});
}, { rootMargin: '100px' });
lazyImages.forEach(img => observer.observe(img));
} else {
// 降级方案:传统scroll + 节流
const lazyLoad = () => {
lazyImages.forEach(img => {
if (img.getBoundingClientRect().top < window.innerHeight + 100 && img.dataset.src) {
img.src = img.dataset.src;
img.onload = () => img.classList.add('loaded');
img.removeAttribute('data-src');
}
});
};
window.addEventListener('scroll', lazyLoad);
window.addEventListener('resize', lazyLoad);
lazyLoad(); // 初始检查
}
})();
CSS占位与过渡
.lazy {
background: #f0f0f0; /* 占位背景 */
transition: opacity 0.3s;
opacity: 0.5;
}
.lazy.loaded {
opacity: 1;
}
性能优化与最佳实践
✅ 5.1 自定预加载距离
{ rootMargin: '200px 0px' } // 提前200px加载,适合高网速环境
根据设备性能调整:低端设备可设为50px,高端设备可设为500px。
✅ 5.2 结合图片尺寸占位
<img data-src="image.jpg" width="800" height="600" />
提前定义宽高,防止懒加载导致布局偏移(CLS)——这是Google排名的重要指标。
✅ 5.3 图片CDN优化
- 使用WebP格式(通过
<picture>标签或服务端协商) - 响应式图片:
srcset结合data-src延迟加载不同分辨率<img data-src="small.jpg" data-srcset="medium.jpg 768w, large.jpg 1024w" ...>
✅ 5.4 懒加载与SEO友好
- 搜索引擎会抓取
data-src中的URL(Google明确说明支持延迟加载属性) - 保持
alt属性完整 - 使用
<noscript>提供降级:<noscript><img src="real-image.jpg" alt="示例"></noscript>
✅ 5.5 组件化封装
推荐使用成熟库:
- lazysizes:轻量、支持响应式、自动检测
- vanilla-lazyload:仅5KB,无依赖
常见问题问答(FAQ)
Q1:懒加载会影响SEO排名吗?
不会,Google明确声明会抓取data-src和data-srcset中的URL,只要确保alt属性存在,且内容与图片关联,就不会被降权,建议同时保留loading="lazy"属性作为额外信号。
Q2:原生loading="lazy"与Intersection Observer哪个更好?
视场景而定:
- 原生方式:零代码、无需兼容性处理(现代浏览器)
- Observer:支持自定义预加载距离、可同时处理iframe/背景图、支持IE降级
建议:优先使用Observer封装通用方案,原生作为降级参考。
Q3:图片大小不一致,如何防止布局抖动?
必须提前定义宽高(如<img width="800" height="600">),如果图片比例不固定,可用aspect-ratioCSS属性配合object-fit:
.lazy-wrap { aspect-ratio: 16/9; }
.lazy-wrap img { object-fit: cover; }
Q4:视频和iframe如何实现懒加载?
视频:将<video>的src属性转为data-src,监听进入视口后赋值。
iframe:替换src为data-src,Observer触发后还原:
iframe.src = iframe.dataset.src; // 注意:iframe重新赋值会重新加载
建议对视频使用preload="none"属性减少初始流量。
Q5:移动端滚动性能如何保证?
- 使用Intersection Observer(无需滚动监听)
- 如果必须用scroll事件,节流间隔设为200ms以上
- 使用
requestAnimationFrame代替scroll回调 - 避免在回调中执行DOM查询(如
querySelectorAll),缓存元素列表
标签: loading="lazy"