一、为什么需要懒加载
想象你打开一个电商网站,首页有上百张商品图片。如果一次性全部加载,不仅浪费流量,还会让页面卡成幻灯片。懒加载就像个精明的管家,只有当图片进入可视区域时才加载,既省流量又提升体验。
核心原理其实很简单:通过IntersectionObserver API或滚动事件监听,判断图片是否出现在视口内。是就加载真实的src,否则先用占位图顶着。
二、原生HTML实现方案
用纯HTML+JS就能搞定,下面是个完整示例(技术栈:Vanilla JS):
<!-- 懒加载图片标签示例 -->
<img
data-src="real-image.jpg" <!-- 真实图片地址 -->
src="placeholder.svg" <!-- 1KB的灰色占位图 -->
class="lazy" <!-- 用于JS选择器定位 -->
alt="商品展示"
>
<script>
// 监听DOM加载完毕
document.addEventListener('DOMContentLoaded', () => {
const lazyImages = document.querySelectorAll('img.lazy');
// 现代浏览器首选方案
if ('IntersectionObserver' in window) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src; // 替换为真实src
observer.unobserve(img); // 加载后停止观察
}
});
});
lazyImages.forEach(img => observer.observe(img));
}
// 兼容旧浏览器的降级方案
else {
let active = false;
const lazyLoad = () => {
if (active) return;
active = true;
setTimeout(() => {
lazyImages.forEach(img => {
if (img.getBoundingClientRect().top <= window.innerHeight) {
img.src = img.dataset.src;
}
});
active = false;
}, 200);
};
document.addEventListener('scroll', lazyLoad);
window.addEventListener('resize', lazyLoad);
}
});
</script>
注意几个关键点:
data-src存储真实URL,避免初始化时立即请求- 占位图要用极小的文件(推荐SVG格式)
- 滚动事件记得做函数节流处理
三、进阶性能优化技巧
3.1 预加载临界区域
对于长页面,可以提前加载视口下方200px范围内的图片:
new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting ||
entry.boundingClientRect.top < window.innerHeight + 200) {
// 加载逻辑同上
}
});
}, { rootMargin: "200px 0px" });
3.2 WebP格式优先加载
通过<picture>标签实现格式降级:
<picture>
<source data-srcset="image.webp" type="image/webp">
<img data-src="image.jpg" src="placeholder.png" class="lazy">
</picture>
3.3 缓存已加载图片
用Map记录已加载的URL,避免重复请求:
const loadedCache = new Map();
function loadImage(img) {
if (loadedCache.has(img.dataset.src)) return;
img.src = img.dataset.src;
loadedCache.set(img.dataset.src, true);
}
四、框架中的优雅实现
以Vue为例(技术栈:Vue 3 + Composition API):
// 封装成自定义指令
app.directive('lazy', {
mounted(el, binding) {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
el.src = binding.value;
observer.disconnect();
}
});
observer.observe(el);
}
});
// 使用示例
<template>
<img v-lazy="'https://example.com/image.jpg'" src="placeholder.png">
</template>
React用户可以用现成库如react-lazyload,或者自己封装:
function LazyImage({ src, placeholder }) {
const [isVisible, ref] = useIntersectionObserver();
return <img
ref={ref}
src={isVisible ? src : placeholder}
/>;
}
// 基于IntersectionObserver的Hook
function useIntersectionObserver() {
const [isVisible, setIsVisible] = useState(false);
const ref = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
setIsVisible(entry.isIntersecting);
});
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);
return [isVisible, ref];
}
五、避坑指南
SEO影响:Google能解析JS渲染的内容,但某些爬虫可能不执行JS。解决方案:
- 服务端渲染时输出真实
<img>标签 - 使用
<noscript>后备方案
- 服务端渲染时输出真实
布局抖动问题:图片加载前后高度变化会导致页面跳动。解决方案:
.lazy-container { aspect-ratio: 16/9; /* 根据图片比例设置 */ background: #f5f5f5; }HTTP/2注意事项:虽然HTTP/2多路复用能缓解请求压力,但移动端仍需要懒加载节省流量
禁用JS的情况:可以通过
<noscript>提供回退:<noscript> <img src="real-image.jpg" alt="..."> </noscript>
六、总结
懒加载不是银弹,需要根据场景选择策略:
- 新闻类网站:适合传统滚动监听
- 电商瀑布流:推荐IntersectionObserver + 预加载
- Web应用:框架级封装更易维护
现代浏览器已经让实现变得非常简单,重点应该关注:
- 精确的触发时机
- 完善的降级方案
- 性能监控(通过Performance API测量实际收益)
最后记住:任何优化都要用数据说话,Lighthouse和WebPageTest是你的好朋友!
评论