懒加载
推迟加载折叠下方内容、图和重组件直到用户需要。
为什么
全量加载的页面加载 3MB 图、200KB 图表库和 80% 用户永不滚到的评论区。懒加载翻转:初始加载仅含可见的(hero、首篇),其余随用户滚动或交互加载。这减初始负载 50-80%,直接改善 LCP 和可交互时间。浏览器原生 loading="lazy" 属性使图懒加载一行改,React.lazy / dynamic import() 使组件懒加载简单。回报即时——页面 1 秒加载替代 3 秒,永不滚到评论的用户永不付 加载它们成本。
何时使用
所有折叠下方图、首屏不需要的重组件(图表、编辑器、地图、视频播放器)和路由级代码分割。非折叠上方内容或 LCP 元素。
如何做
1. 用原生 loading="lazy" 属性懒加载图
- <img src="photo.jpg" loading="lazy" width="800" height="600" />
- 始终含宽高防布局偏移(CLS)
- LCP 图设 fetchpriority="high" 优先化
- 用模糊占位(blur-up)改善感知体验
2. 用 React.lazy 和 Suspense 懒加载组件
- const Chart = lazy(() => import("./HeavyChart"));
- 包 <Suspense fallback={<Skeleton />}> 加载时显占位
- 交互(点击)或滚入视(IntersectionObserver)时加载非挂载时
3. 用动态导入懒加载路由
- 每路由单独块,仅导航到时加载
- 空闲时预取下一可能路由(requestIdleCallback)供即时导航
- 用框架内置代码分割(Next.js dynamic、Vite manualChunks)
4. 用 IntersectionObserver 懒加载数据
- 评论区滚入视前不取评论
- const observer = new IntersectionObserver(callback); observer.observe(el);
- 用户接近底部时加载列表下一页(无限滚动)
5. 测影响
- 比较懒加载前后 LCP 和总阻塞时间
- 查 Network 标签: 初始加载应更少更小请求
- 验证滚动触发懒加载(非反正全加载的定时器)示例
// 懒图: 原生 loading="lazy"
<img src="hero.jpg" fetchpriority="high" width="1200" height="600" alt="Hero" />
<img src="article-1.jpg" loading="lazy" width="800" height="500" alt="Article 1" />
<img src="article-2.jpg" loading="lazy" width="800" height="500" alt="Article 2" />
// 懒组件: 仅节可见时加载图表
import { lazy, Suspense, useState, useEffect, useRef } from "react";
const HeavyChart = lazy(() => import("./HeavyChart"));
function ChartSection() {
const [visible, setVisible] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) setVisible(true);
}, { rootMargin: "100px" }); // 可见前 100px 开始加载
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);
return (
<div ref={ref}>
{visible && (
<Suspense fallback={<ChartSkeleton />}>
<HeavyChart data={data} />
</Suspense>
)}
</div>
);
}
// 懒路由: 空闲时预取供即时导航
const Settings = lazy(() => import("./routes/Settings"));
// 用户可能导航时预取(如悬停链接)
function SettingsLink() {
return (
<Link
to="/settings"
onMouseEnter={() => requestIdleCallback(() => import("./routes/Settings"))}
>
Settings
</Link>
);
}常见问题
- ⚠懒加载 LCP 元素(hero 图)延迟最重要内容——LCP 应急切加载带 fetchpriority="high" 非懒。
- ⚠无占位懒加载(无 Suspense fallback、无骨架)致内容加载时空白闪或布局跳,增 CLS 退化体验。
- ⚠用定时器触发懒加载(setTimeout 3 秒后全加载)违背目的——用户等 3 秒后全加载,同急切但额外延迟。
- ⚠无宽高懒加载图致加载时布局偏移——下方内容跳下、CLS 升、用户误点。
最佳实践
- ✓急切加载 LCP 元素(hero 图、主标题)带 fetchpriority="high" 折叠下方全懒加载——LCP 是用户首见,懒加载它违背目的。
- ✓始终提供 Suspense fallback 或骨架预留与懒内容同空间——这防布局偏移并给用户内容加载中即时反馈。
- ✓用带 rootMargin(如 200px)的 IntersectionObserver 在元素全可见前开始加载——使懒加载感即时,内容在用户滚到时已就绪。
- ✓空闲时预取下一可能路由(链接悬停时 requestIdleCallback)使导航感即时——用户点击时块已在浏览器缓存。