本文是「从零打造 AI 全栈应用」系列第 七 篇。
在上一篇中,我们完成了 NestJS + Prisma 的企业级 API 架构搭建。从这一篇开始,我们会回到性能层,解决一个在真实项目中必须做、而且极容易被低估的问题:
图片懒加载(Image Lazy Loading) 。
如果你做的是:
- 信息流 / 文章列表
- AI 内容生成平台
- 图文混排、卡片流页面
那么图片懒加载不是“优化项”,而是上线前的基本要求。
一、为什么图片一定要做懒加载?
先说结论:
列表页不做图片懒加载 = 性能事故。
1️⃣ 浏览器加载图片的真实成本
<img src="xxx.jpg" />
这行代码的背后意味着:
- 一次 HTTP 请求
- 占用浏览器并发连接数
- 图片解码 + 内存占用
如果首页有 30 张图:
- HTML 还没解析完
- CSS / JS 还没执行
- 图片请求已经打满并发
👉 首屏直接变慢。
2️⃣ 首屏加载的核心原则
首屏优先,非首屏延后。
因此图片加载必须遵守:
- 首屏图片:可以加载
- viewport 之外的图片:绝对不要加载
这正是图片懒加载要解决的问题。
二、传统方案:onscroll + 节流(为什么不推荐)
早期懒加载的写法通常是:
- 监听
scroll事件 - 计算元素位置
- 判断是否进入视窗
- 手动替换
src
问题在于:
scroll触发频率极高- 需要手动节流 / 防抖
- 计算逻辑复杂且易错
这是典型的“能跑,但不该在现代项目里用”的方案。
三、现代标准解法:IntersectionObserver
1️⃣ IntersectionObserver 是什么?
它是浏览器原生提供的一个 API,核心思想是:
观察某个元素,是否进入视口(viewport)。
关键词拆解:
- Observer:观察者(设计模式)
- Intersection:与视窗产生交集
浏览器帮你完成了:
- 滚动监听
- 位置计算
- 性能优化
我们只需要关心:
元素“出现了”这一刻。
四、原生图片懒加载完整实现
1️⃣ HTML 结构设计
<img
class="lazy"
src="placeholder.png"
data-src="real-image.jpg"
/>
设计要点:
src:占位图(体积小)data-src:真实图片地址
2️⃣ JS:实例化 IntersectionObserver
const images = document.querySelectorAll('.lazy');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
images.forEach(img => observer.observe(img));
3️⃣ 这段代码做了什么?
- 浏览器自动监听滚动
- 元素进入 viewport
isIntersecting === true- 替换真实图片地址
- 加载完成后取消观察
没有 scroll,没有节流,性能几乎零成本。
五、为什么一定要 unobserve?
这是一个非常容易被忽略的点。
如果不取消观察:
- Observer 会一直持有引用
- 页面越滚越多,观察对象越多
- 长列表会产生内存浪费
👉 进入一次,加载一次,然后立刻释放。
这是专业写法。
六、React 项目中的图片懒加载
在真实项目中,我们通常不会手写原生逻辑,而是:
- 用成熟组件
- 统一行为
- 减少心智负担
1️⃣ react-lazy-load 的本质
pnpm i react-lazy-load
这个库本质上:
内部仍然是 IntersectionObserver。
你获得的是:
- 组件化封装
- 更好的可读性
- 与 React 生命周期自然融合
七、实战示例
<LazyLoad className="w-full h-full">
<img
loading="lazy"
src={post.thumbnail}
className="w-full h-full object-cover"
/>
</LazyLoad>
为什么这里是“双保险”?
1️⃣ LazyLoad 组件
- 控制是否渲染
- IntersectionObserver 触发
2️⃣ loading="lazy"
- 浏览器原生兜底
- 即使 JS 失效,也不会一次性加载
工程级优化的特点就是:多层防线。
八、为什么列表页一定要做懒加载?
以文章列表为例:
- 首屏只展示 3~5 条
- 但接口一次返回 20 条
- 如果全部图片同时加载
结果就是:
- 首屏白屏时间变长
- 滚动明显卡顿
- 移动端直接掉帧
懒加载不是“优化体验”,而是“保证可用”。
九、面试官视角:你应该怎么回答?
如果我在面试中问你:
“你们项目是怎么做图片懒加载的?”
一个合格的回答应该包括:
- 为什么不能一次性加载
- IntersectionObserver 的原理
- 占位图 + data-src 思路
- React 中的组件化封装
- loading="lazy" 的兜底作用
能讲到这一层,说明你不是“背八股”,而是真的做过性能优化。
总结
图片懒加载,是一个:
- 代码不多
- 价值极高
- 非常容易被忽略
但真正专业的前端,一定会主动去做的优化点。