第一次接手公司官网改版项目时,我遇到了一个典型的新手困境——首页加载速度始终在3秒以上徘徊。通过Chrome DevTools的Performance面板分析,发现90%的加载时间都消耗在了首屏不可见区域的图片资源上。这正是懒加载技术要解决的核心痛点:让视口外的图片仅在需要显示时才加载。
现代网页中图片平均占比超过60%,电商类网站甚至高达80%。传统同步加载方式会一次性请求所有图片资源,造成三大性能瓶颈:
我在某跨境电商项目实测数据显示,引入懒加载后:
这是现代浏览器提供的性能最优解,通过异步观察目标元素与视口的交叉状态实现。核心优势在于不依赖滚动事件,避免主线程阻塞:
javascript复制const lazyImages = document.querySelectorAll('img.lazy');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
lazyImages.forEach(img => observer.observe(img));
关键参数说明:threshold默认0表示刚进入视口触发,可设为0.1提前加载;rootMargin可扩展视口检测范围
兼容性更好的传统方案,适合需要支持老旧浏览器的场景:
javascript复制function lazyLoad() {
const images = document.querySelectorAll('img[data-src]');
images.forEach(img => {
const rect = img.getBoundingClientRect();
if (rect.top < window.innerHeight + 200) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
}
});
}
window.addEventListener('scroll', throttle(lazyLoad, 200));
必须配合节流函数使用(如lodash.throttle),否则高频滚动会引发性能问题
| 库名称 | 体积 | 特性 | 适用场景 |
|---|---|---|---|
| lazysizes | 4.3KB | 自动检测、响应式支持 | 复杂图片布局 |
| lozad.js | 1.5KB | 纯观察器、无依赖 | 轻量级项目 |
| vue-lazyload | 18KB | Vue指令、过渡动画 | Vue技术栈 |
结合srcset实现分辨率适配,避免移动端加载桌面版大图:
html复制<img
data-src="placeholder.jpg"
data-srcset="small.jpg 480w, medium.jpg 768w, large.jpg 1200w"
sizes="(max-width: 600px) 480px, 800px"
class="lazy"
alt="自适应图片示例">
避免布局抖动(CLS)的CSS方案:
css复制.lazy-container {
position: relative;
aspect-ratio: 16/9; /* 保持宽高比 */
background: #f5f5f5;
}
.lazy-img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
transition: opacity 0.3s;
opacity: 0;
}
.lazy-img.loaded {
opacity: 1;
}
现代浏览器支持图片异步解码,避免渲染卡顿:
javascript复制img.decode().then(() => {
img.classList.add('loaded');
}).catch(() => {
// 异常处理
});
错误做法:所有图片初始都用data-src,导致爬虫无法索引
正确方案:
html复制<!-- 首屏关键图片正常加载 -->
<img src="hero.jpg" alt="主视觉图">
<!-- 非关键图片懒加载 -->
<img data-src="product.jpg" alt="产品图" class="lazy">
使用IntersectionObserver时必须清理:
javascript复制// 组件卸载时
observer.disconnect();
必备的错误监听和重试机制:
javascript复制img.onerror = function() {
this.src = 'fallback.jpg';
// 或显示错误占位图
};
确保打印时所有图片正常显示:
css复制@media print {
.lazy {
opacity: 1 !important;
}
img[data-src] {
content: attr(data-src);
}
}
通过PerformanceObserver监控LCP变化:
javascript复制const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'largest-contentful-paint') {
console.log('LCP时间:', entry.startTime);
}
}
});
po.observe({type: 'largest-contentful-paint', buffered: true});
通过data-attribute+CSS变量实现:
html复制<div
class="lazy-bg"
data-bg="url('hero-bg.jpg')">
</div>
<style>
.lazy-bg.loaded {
background-image: var(--bg-src);
}
</style>
<script>
document.querySelector('.lazy-bg').style.setProperty(
'--bg-src',
document.querySelector('.lazy-bg').dataset.bg
);
</script>
结合poster属性优化:
html复制<video
controls
poster="placeholder.jpg"
data-poster="real-poster.jpg">
<source src="video.mp4" type="video/mp4">
</video>
配合IntersectionObserver的rootMargin预加载:
javascript复制new IntersectionObserver(callback, {
rootMargin: '500px 0px' // 提前500px加载
});
在最近一次Vue3项目中,我们通过组合式API封装了复用性极高的懒加载指令:
javascript复制// useLazyLoad.js
export default {
mounted(el, binding) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
el.src = binding.value;
observer.unobserve(el);
}
});
}, {
threshold: 0.1,
rootMargin: '200px'
});
observer.observe(el);
}
}