本文 AI 產出,尚未審核
JavaScript – 效能與最佳化(Performance & Optimization)
主題:Lazy Loading(懶載入)
簡介
在前端開發中,效能往往是使用者體驗的關鍵。頁面載入過慢、資源過度下載,都會直接導致跳出率上升。Lazy loading(懶載入)是一種讓資源 僅在需要時才載入 的技巧,能顯著降低首次渲染時間(First Paint)與總下載量。
隨著單頁應用(SPA)與大量圖片、影片、第三方套件的普及,懶載入已從「可有可無」變成 必備的最佳化手段。掌握它,不僅能提升 SEO,還能減少行動裝置的流量消耗,讓使用者感受到更順暢的互動。
核心概念
1. 為什麼要懶載入?
| 傳統載入方式 | 懶載入方式 |
|---|---|
| 於頁面載入時一次下載所有資源 | 只下載當前視窗可見或即將可見的資源 |
| 下載量大、首屏渲染慢 | 首屏渲染快、帶寬利用率高 |
| 影響 SEO(Google 會降低未被渲染的資源評分) | 只載入必要資源,Google 能正確抓取內容 |
重點:懶載入的核心是「延遲」而不是「省略」——資源仍會在適當時機被載入,只是推遲到使用者真的需要它的時候。
2. 懶載入的分類
| 類型 | 說明 | 常見應用 |
|---|---|---|
| 圖片懶載入 | 只在圖片進入或即將進入視窗時才請求 | 網站相簿、商品列表 |
| 模組懶載入(Code Splitting) | 依路由或條件動態載入 JavaScript 模組 | SPA、React/Vue 路由 |
| 影片/音訊懶載入 | 只在使用者點擊播放或滾動到可見區域時載入媒體檔案 | 影音平台、教學網站 |
| 第三方腳本懶載入 | 延遲載入分析、廣告、社群外掛等外部腳本 | 測試版、A/B 測試環境 |
以下將以 圖片懶載入、模組懶載入、影片懶載入 為例,示範不同實作方式。
3. 實作方式概覽
- 原生
loading="lazy"(僅支援圖片與<iframe>) - Intersection Observer API(瀏覽器原生觀測元素是否進入視口)
- 動態
import()(ESM 的模組懶載入) - 第三方函式庫(如
lazysizes、react-loadable)
程式碼範例
範例 1:原生 loading="lazy"(最簡單)
<!-- 只要加上 loading="lazy",瀏覽器會自動在圖片即將出現在視窗時載入 -->
<img src="large-photo.jpg" alt="風景照" loading="lazy" width="600" height="400">
說明:
- 只支援現代瀏覽器(Chrome、Edge、Firefox 75+)
- 無需額外 JavaScript,對 SEO 完全友好
範例 2:使用 Intersection Observer 實作圖片懶載入
// 1. 取得所有待懶載入的圖片(使用 data-src 暫存實際 URL)
const lazyImages = document.querySelectorAll('img[data-src]');
// 2. 建立 observer
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
// 把 data-src 轉成 src,觸發下載
img.src = img.dataset.src;
// 下載完成後移除 data-src,避免重複觀測
img.removeAttribute('data-src');
// 停止觀測此元素
obs.unobserve(img);
}
});
}, {
rootMargin: '0px 0px 200px 0px', // 先於 200px 前載入
threshold: 0.1
});
// 3. 讓 observer 觀測每張圖片
lazyImages.forEach(img => observer.observe(img));
<!-- HTML 範例 -->
<img data-src="large-photo-1.jpg" alt="海灘" width="800" height="600">
<img data-src="large-photo-2.jpg" alt="山脈" width="800" height="600">
要點:
rootMargin可提前載入,避免使用者滾動時看到空白threshold設為0.1表示只要 10% 進入視口即觸發
範例 3:模組懶載入(Code Splitting)
// 假設有一個大型的圖表套件 chart.js,平常不需要
// 只在使用者點擊「顯示圖表」時才載入
const showChartButton = document.getElementById('showChart');
showChartButton.addEventListener('click', async () => {
// 動態 import,回傳一個 Promise,解構出需要的模組
const { renderChart } = await import('./chart.js');
renderChart('#chartContainer');
});
// chart.js
export function renderChart(containerSelector) {
const container = document.querySelector(containerSelector);
// 假設這裡有大量的 D3.js 或 Highcharts 程式碼
container.innerHTML = '<svg>…圖表內容…</svg>';
}
說明:
import()只會在點擊時執行,瀏覽器會自動把chart.js拆分成獨立的 chunk- 這樣的做法在大型 SPA 中可減少首屏 JS 包大小,提升載入速度
範例 4:影片懶載入(使用 <video> + IntersectionObserver)
const lazyVideos = document.querySelectorAll('video[data-src]');
const videoObserver = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const video = entry.target;
// 設定 src,開始載入
video.src = video.dataset.src;
video.load(); // 立即開始緩衝
video.removeAttribute('data-src');
obs.unobserve(video);
}
});
}, {
rootMargin: '0px 0px 300px 0px',
threshold: 0.25
});
lazyVideos.forEach(v => videoObserver.observe(v));
<video controls width="640" height="360" data-src="movie-1080p.mp4" poster="thumb.jpg">
您的瀏覽器不支援影片播放。
</video>
技巧:
- 使用
poster屬性顯示縮圖,避免影片未載入時的空白rootMargin設大一點,可在使用者滾動前先緩衝,提升即點即播的感受
範例 5:第三方函式庫 – lazysizes(支援圖片、iframe、背景圖)
<!-- 引入 lazysizes(建議放在 body 結尾) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.3.2/lazysizes.min.js" async></script>
<!-- 使用 data-src、data-srcset、lazyload 類別 -->
<img
data-src="photo-large.jpg"
data-srcset="photo-400w.jpg 400w, photo-800w.jpg 800w"
class="lazyload"
alt="城市夜景"
width="1200"
height="800">
優點:
- 自動偵測瀏覽器支援
loading="lazy",若不支援則使用 IntersectionObserver- 支援
srcset、picture、背景圖(data-bg)等多種情境- 可透過插件擴充(如
ls.unveilhooks、ls.parent-fit)
常見陷阱與最佳實踐
| 陷阱 | 風險 | 解決方案 |
|---|---|---|
忘記設定 alt |
無障礙與 SEO 受損 | 懶載入不影響 alt,仍須完整描述 |
| 懶載入過度(每個小圖都懶載) | 觀測器數量過多,造成記憶體與 CPU 壓力 | 只對 大型、首次不可見 的資源使用懶載 |
根據 rootMargin 設定過小 |
使用者快速滾動時會看到空白 | 調整 rootMargin(如 200px)讓瀏覽器提前下載 |
在 SSR(Server‑Side Rendering)環境中使用 window |
產生 ReferenceError | 只在 useEffect(React)或 componentDidMount(Vue)內使用 |
| 懶載入的資源被快取失效 | 每次載入都重新下載 | 設定適當的 HTTP Cache-Control,或使用 Service Worker 管理快取 |
最佳實踐
- 分層懶載:先對圖片、影片等大量媒體使用懶載,對核心功能(如導航、關鍵 JS)保持同步載入。
- 預載關鍵資源:使用
<link rel="preload">或fetch()提前載入即將需要的模組,避免點擊時的卡頓。 - 結合
requestIdleCallback:在瀏覽器空閒時載入非關鍵資源,減少主執行緒阻塞。 - 測試與監控:利用 Lighthouse、WebPageTest 或 Chrome DevTools 的 Performance 面板,確認懶載的觸發時機與實際下載量。
- 考慮行動裝置:行動網路頻寬有限,適度降低圖片解析度(
srcset)或使用 WebP/AVIF 格式。
實際應用場景
電商平台
- 商品列表往往包含上千張縮圖,使用 IntersectionObserver 懶載可將首次載入時間縮短 30% 以上。
- 商品詳情頁的高解析度圖、影片僅在使用者點擊「放大」或「觀看影片」時才下載,降低流量成本。
部落格與新聞網站
- 文章內的圖片與嵌入影片使用
loading="lazy"或lazysizes,提升首屏渲染速度,對 SEO 有正面影響。
- 文章內的圖片與嵌入影片使用
單頁應用(SPA)
- 依路由分割程式碼(React.lazy、Vue 的動態
import()),只在使用者切換到特定頁面時載入對應的 UI 套件或圖表庫。
- 依路由分割程式碼(React.lazy、Vue 的動態
大型儀表板(Dashboard)
- 當使用者切換不同報表時,透過模組懶載入分別載入 Highcharts、D3、ECharts 等重型套件,避免一次性載入全部圖表程式碼。
教學平台
- 課程影片採用懶載,先載入縮圖與字幕檔,使用者點擊播放才下載影片本體,降低課程預覽時的流量。
總結
Lazy loading 是 提升前端效能的關鍵武器,從圖片、影片到 JavaScript 模組,都可以藉由延遲載入減少首屏資源、縮短載入時間、節省頻寬。
- 原生屬性 (
loading="lazy") 提供最簡單的入口,適合小型專案。 - Intersection Observer 為最通用、彈性的解決方案,能自訂觸發時機與回呼行為。
- 動態
import()則是模組層面的懶載,讓大型 SPA 的程式碼分割變得輕鬆。 - 第三方函式庫 如
lazysizes能快速支援多種資源類型,且兼容舊版瀏覽器。
在實務開發時,務必 避免過度懶載、設定合適的觸發阈值,並結合 快取策略 與 預載,才能在效能與使用者體驗之間取得最佳平衡。
實戰建議:先從最容易實作的圖片懶載(原生
loading="lazy")開始,逐步導入 IntersectionObserver 與模組懶載,最後根據需求加入 Service Worker、requestIdleCallback等進階技巧,讓你的網站在速度、流量與 SEO 上都能更上一層樓。