摘要: 在网页开发中,首屏加载速度直接决定了用户的留存率。当页面中充斥着大量高分辨率图片时,如何避免“瀑布流”式的漫长等待?本文将带你从零构建一个基于 IntersectionObserver 的图片懒加载方案,告别传统的 onscroll 事件监听,用设计模式的思想解决性能痛点。
1. 为什么我们需要“懒加载”?🐢
在传统的网页加载流程中,浏览器会解析 HTML 并立即请求 <img> 标签中的 src 属性。这会导致一个严重的问题:
- 资源浪费: 即使图片位于屏幕可视区域(Viewport)之外,浏览器也会发起 HTTP 请求下载它。
- 首屏阻塞: 大量的图片请求占用了带宽和并发连接数,导致 HTML、CSS 和首屏关键资源加载变慢。
- 用户体验差: 用户看到的是一个缓慢加载、甚至“卡顿”的页面。
解决方案: 让视窗外的图片“偷个懒”,只有当用户滚动到它附近时,再加载真实的图片资源。
2. 传统方案 vs 现代方案 📊
| 方案 | 原理 | 缺点 |
|---|---|---|
| 传统 OnScroll | 监听 window.onscroll 事件,通过 getBoundingClientRect 计算位置 | 性能杀手:滚动事件触发频率极高,频繁计算 DOM 位置会导致页面卡顿(重排/重绘)。 |
| 现代 IntersectionObserver | 浏览器原生提供的观察者模式 API | 高性能:由浏览器内核在渲染层直接处理,不占用主线程,无性能问题。 |
💡 核心概念:
IntersectionObserver是一种异步观察机制。它不需要我们手动去“问”浏览器“图片在视窗里了吗?”,而是浏览器会主动“告诉”我们“图片进入视窗了”。
3. 代码实战:从零实现懒加载 🛠️
下面我们通过一个简单的 Demo,手把手教你实现一个高性能的图片懒加载组件。
第一步:HTML 结构(数据分离)
我们要打破直接在 src 写真实地址的习惯。利用 HTML5 的 data-* 属性来存储真实地址,src 只放一个极小的占位图(Placeholder)。
<!-- 占位图(极小,优先加载) -->
<!-- 真实图地址(存在 data-src 里,不发起请求) -->
<img
class="lazy"
src="https://img10.360buyimg.com/wq/jfs/t24601/190/890984006/4559/731564fc/5b7f9b7bN3ccd29ab.png"
data-src="https://img.36krcdn.com/hsossms/20260119/真实高清大图地址"
alt=""
>
第二步:JavaScript 核心逻辑
这是本文的核心部分。我们利用 IntersectionObserver 来监听图片元素。
// 1. 找到所有需要懒加载的图片
const images = document.querySelectorAll('.lazy');
// 2. 实例化观察者对象
// entries: 所有被观察元素的状态集合
// isIntersecting: 布尔值,表示该元素是否与视窗发生交叉(即出现在视窗中)
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 3. 如果图片出现在视窗中
const img = entry.target;
const originalSrc = img.dataset.src; // 获取真实地址
console.log('加载真实图片:', originalSrc);
// 4. 将 data-src 的值赋给 src,触发图片加载
img.src = originalSrc;
// 5. 加载完成后,取消对该图片的观察(优化性能)
observer.unobserve(img);
}
});
});
// 3. 开始观察:给每一张图片加上观察指令
images.forEach(img => observer.observe(img));
4. 原理深度解析 🔍
让我们拆解一下这段代码背后的逻辑:
-
观察者模式 (Observer Pattern):
- 观察者 (Observer):
new IntersectionObserver(...)实例。 - 被观察者 (Target): 所有的
<img class="lazy">元素。 - 回调 (Callback): 当被观察者的状态(与视窗的交叉状态)发生变化时,浏览器会自动调用构造函数中传入的函数。
- 观察者 (Observer):
-
生命周期控制:
- 出现即加载: 利用
entry.isIntersecting判断图片是否进入视野。 - 及时止损: 一旦图片加载完成(
img.src = originalSrc),立即调用observer.unobserve(img)。这样做的好处是,图片加载后,观察者就不再关心这张图片了,减少了后续的计算开销。
- 出现即加载: 利用
-
数据属性 (Dataset):
- 使用
img.dataset.src来读取data-src属性。这是标准的 DOM 操作方式,比getAttribute更加语义化。
- 使用
5. 总结与展望 📝
通过使用 IntersectionObserver,我们实现了一个无感知、高性能的图片懒加载方案。
- 优势: 代码简洁,性能优异,不阻塞主线程。
- 适用场景: 电商首页、图片瀑布流、长文阅读页面。
- 未来展望: 随着 Web API 的发展,我们可以进一步结合
loading="lazy"(原生 HTML 懒加载属性)作为降级方案,或者结合 WebP/AVIF 格式实现更极致的图片加载体验。
一句话总结: 让浏览器去做它擅长的事(计算位置),让开发者专注于业务逻辑(加载图片),这就是现代前端开发的魅力所在。