本文 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. 實作方式概覽

  1. 原生 loading="lazy"(僅支援圖片與 <iframe>
  2. Intersection Observer API(瀏覽器原生觀測元素是否進入視口)
  3. 動態 import()(ESM 的模組懶載入)
  4. 第三方函式庫(如 lazysizesreact-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
  • 支援 srcsetpicture、背景圖(data-bg)等多種情境
  • 可透過插件擴充(如 ls.unveilhooksls.parent-fit

常見陷阱與最佳實踐

陷阱 風險 解決方案
忘記設定 alt 無障礙與 SEO 受損 懶載入不影響 alt,仍須完整描述
懶載入過度(每個小圖都懶載) 觀測器數量過多,造成記憶體與 CPU 壓力 只對 大型首次不可見 的資源使用懶載
根據 rootMargin 設定過小 使用者快速滾動時會看到空白 調整 rootMargin(如 200px)讓瀏覽器提前下載
在 SSR(Server‑Side Rendering)環境中使用 window 產生 ReferenceError 只在 useEffect(React)或 componentDidMount(Vue)內使用
懶載入的資源被快取失效 每次載入都重新下載 設定適當的 HTTP Cache-Control,或使用 Service Worker 管理快取

最佳實踐

  1. 分層懶載:先對圖片、影片等大量媒體使用懶載,對核心功能(如導航、關鍵 JS)保持同步載入。
  2. 預載關鍵資源:使用 <link rel="preload">fetch() 提前載入即將需要的模組,避免點擊時的卡頓。
  3. 結合 requestIdleCallback:在瀏覽器空閒時載入非關鍵資源,減少主執行緒阻塞。
  4. 測試與監控:利用 Lighthouse、WebPageTest 或 Chrome DevTools 的 Performance 面板,確認懶載的觸發時機與實際下載量。
  5. 考慮行動裝置:行動網路頻寬有限,適度降低圖片解析度(srcset)或使用 WebP/AVIF 格式。

實際應用場景

  1. 電商平台

    • 商品列表往往包含上千張縮圖,使用 IntersectionObserver 懶載可將首次載入時間縮短 30% 以上。
    • 商品詳情頁的高解析度圖、影片僅在使用者點擊「放大」或「觀看影片」時才下載,降低流量成本。
  2. 部落格與新聞網站

    • 文章內的圖片與嵌入影片使用 loading="lazy"lazysizes,提升首屏渲染速度,對 SEO 有正面影響。
  3. 單頁應用(SPA)

    • 依路由分割程式碼(React.lazy、Vue 的動態 import()),只在使用者切換到特定頁面時載入對應的 UI 套件或圖表庫。
  4. 大型儀表板(Dashboard)

    • 當使用者切換不同報表時,透過模組懶載入分別載入 Highcharts、D3、ECharts 等重型套件,避免一次性載入全部圖表程式碼。
  5. 教學平台

    • 課程影片採用懶載,先載入縮圖與字幕檔,使用者點擊播放才下載影片本體,降低課程預覽時的流量。

總結

Lazy loading提升前端效能的關鍵武器,從圖片、影片到 JavaScript 模組,都可以藉由延遲載入減少首屏資源、縮短載入時間、節省頻寬。

  • 原生屬性 (loading="lazy") 提供最簡單的入口,適合小型專案。
  • Intersection Observer 為最通用、彈性的解決方案,能自訂觸發時機與回呼行為。
  • 動態 import() 則是模組層面的懶載,讓大型 SPA 的程式碼分割變得輕鬆。
  • 第三方函式庫lazysizes 能快速支援多種資源類型,且兼容舊版瀏覽器。

在實務開發時,務必 避免過度懶載設定合適的觸發阈值,並結合 快取策略預載,才能在效能與使用者體驗之間取得最佳平衡。

實戰建議:先從最容易實作的圖片懶載(原生 loading="lazy")開始,逐步導入 IntersectionObserver 與模組懶載,最後根據需求加入 Service Worker、requestIdleCallback 等進階技巧,讓你的網站在速度、流量與 SEO 上都能更上一層樓。