魯斯前端布魯斯前端

文章中英模式

常見的前端面試題目 - 頁面載入 - Lazy Loading 是什麼?

深入解析前端 Lazy Loading 優化技術:圖片 Lazy Loading 、組件 Lazy Loading 及 IntersectionObserver API 的原理實現,以及 Next.js 代碼分割技術的完整剖析。

影片縮圖

懶得看文章?那就來看影片吧

基本概念

Lazy Loading(懶加載)是優化網頁性能的技術,核心是延遲加載非必要資源(有看到才加載,沒看到先等等),等到真正需要時才載入。這樣能大幅提升頁面初始載入速度,省下資源,讓用戶體驗更好。

傳統加載流程:
所有資源同時加載 → 頁面渲染完成 → 用戶可交互

Lazy Loading 流程:
關鍵資源加載(有看到才加載) → 頁面初步渲染 → 用戶可交互 → 其他資源按需加載

前端開發中, Lazy Loading 主要應用場景:

應用場景實際例子
圖片和媒體檔案電商網站的產品圖片、社交媒體的影片內容
UI 組件和頁面區塊頁面底部的評論區、彈出式對話框

圖片 Lazy Loading

傳統圖片載入 vs Lazy Loading

傳統網頁會一次性下載所有 <InlineCode><img></InlineCode> 標籤的圖片,無論是否可見,浪費資源且降低頁面載入速度。

傳統圖片加載:
┌──────────────────────────────────────────┐
│                                          │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐│  所有圖片
│  │ 圖片 1    │  │ 圖片 2    │  │ 圖片 3    ││  同時請求
│  └──────────┘  └──────────┘  └──────────┘│
│                                          │◄───────
│  ┌──────────┐  ┌──────────┐  ┌──────────┐│  用戶僅
│  │ 圖片 4    │  │ 圖片 5    │  │ 圖片 6    ││  看到這裡
│  └──────────┘  └──────────┘  └──────────┘│
│                                          │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐│  未顯示的
│  │ 圖片 7    │  │ 圖片 8    │  │ 圖片 9    ││  也被加載
│  └──────────┘  └──────────┘  └──────────┘│
│                                          │
└──────────────────────────────────────────┘

Lazy Loading 則只在圖片接近或進入視窗時才加載,大幅減少初始頁面負載。

 Lazy Loading 圖片:
┌──────────────────────────────────────────┐
│                                          │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐│  已載入
│  │ 圖片 1    │  │ 圖片 2    │  │ 圖片 3    ││  (在視窗中)
│  └──────────┘  └──────────┘  └──────────┘│
│                                          │◄───────
│  ┌──────────┐  ┌──────────┐  ┌──────────┐│  正在載入
│  │ 圖片 4    │  │ 載入中... │  │ 載入中... ││  (接近視窗)
│  └──────────┘  └──────────┘  └──────────┘│
│                                          │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐│  尚未載入
│  │ 未載入    │  │ 未載入    │  │ 未載入    ││  (遠離視窗)
│  └──────────┘  └──────────┘  └──────────┘│
│                                          │
└──────────────────────────────────────────┘

實現圖片 Lazy Loading 的方式

1. 原生 loading='lazy' 屬性

現代瀏覽器內建支援的 Lazy Loading 屬性,簡單易用,無需額外 JavaScript。

<img src="image.jpg" loading="lazy" alt=" Lazy Loading 圖片" />

2. IntersectionObserver API

更靈活的 Lazy Loading 方式,監控元素是否進入視窗,效能好。

// 圖片 Lazy Loading 基本實現
const lazyImages = document.querySelectorAll('.lazy-image');

const imageObserver = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src; // 載入真實圖片
      img.classList.remove('lazy-image');
      observer.unobserve(img); // 載入後停止觀察
    }
  });
});

// 對每個 Lazy Loading 圖片進行觀察
lazyImages.forEach(img => {
  imageObserver.observe(img);
});

組件 Lazy Loading

Skeleton 佔位符技術

Skeleton(骨架屏)是組件載入中的過渡技術,在內容載入前顯示輪廓,讓用戶等待體驗更好。

 Lazy Loading 組件流程:
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│                 │     │ ░░░░░░░░░░░░░░░ │     │ 標題文字         │
│                 │     │ ░░░░░░░░░░░     │     │ 實際內容段落     │
│    空白區域      │ ──► │ ░░░░░░░░░░░░░░░ │ ──►  │ 圖片和其他       │
│  (不好的體驗)    │     │ ░░░░░░░░░░░     │      │ 交互元素         │
│                 │     │ ░░░░░░░░░░░░░░░ │     │                 │
└─────────────────┘     └─────────────────┘     └─────────────────┘
      初始狀態            Skeleton 佔位           完整內容載入

好的 Skeleton 設計有以下特點:

  • 1. 和實際內容有相似的結構
  • 2. 用動畫效果提示「正在載入」
  • 3. 用適當的顏色,不干擾視覺
function CardSkeleton() {
  return (
    <div className="card-skeleton">
      <div className="skeleton-img pulse"></div>
      <div className="skeleton-title pulse"></div>
      <div className="skeleton-text pulse"></div>
      <div className="skeleton-text pulse"></div>
    </div>
  );
}

function ProductCard({ loading, product }) {
  if (loading) {
    return <CardSkeleton />;
  }
  
  return (
    <div className="product-card">
      <img src={product.image} alt={product.name} />
      <h3>{product.name}</h3>
      <p>{product.description}</p>
      <button>加入購物車</button>
    </div>
  );
}

Window.IntersectionObserver API 深入理解

IntersectionObserver 是實現 Lazy Loading 的好工具,它能監測元素與視窗的交叉狀態變化。

IntersectionObserver 工作原理:
┌─────────────────────────────────────────┐
│                                         │
│                視口 (Viewport)           │
│                                         │
│  ┌─────────────────────────────────┐    │
│  │                                 │    │
│  │  可見區域 (Visible Area)         │    │
│  │                                 │    │
│  └─────────────────────────────────┘    │
│                   ▲                     │
│                   │                     │
│  ┌─────────────┐  │  ┌─────────────┐    │
│  │ 元素 A       │  │  │ 元素 B      │    │
│  │ isInter-    │  │  │ isInter-    │    │
│  │ secting:    │  │  │ secting:    │    │
│  │ false       │  │  │ true        │    │
│  └─────────────┘  │  └─────────────┘    │
│                   │                     │
└───────────────────┼─────────────────────┘
                交叉狀態變化時觸發回調

核心優勢

  • 1. 減少初始頁面載入時間
  • 2. 降低伺服器負擔和省流量
  • 3. 在手機上效能更好
  • 4. 減少資源浪費(用戶可能永遠不會滾到某些內容)
// IntersectionObserver 配置示例
const options = {
  root: document.querySelector('#scrollArea'), // 默認為視窗
  rootMargin: '0px 0px 100px 0px', // 提前100px觸發
  threshold: [0, 0.5, 1] // 交叉比例閾值
};

const observer = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    // 元素進入視窗
    if (entry.isIntersecting) {
      console.log('元素進入視窗,可見比例: ' + entry.intersectionRatio);
    }
  });
}, options);

// 開始觀察元素
observer.observe(document.querySelector('#targetElement'));

典型應用場景

  1. 1. 圖片/影片 Lazy Loading :元素進入視窗才載入
  2. 2. 無限滾動:到底部就跟後端獲取數據,載入更多內容
// 無限滾動示例
function createInfiniteScroll(callback) {
  // 創建觀察者
  const observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        // 觸發加載更多內容
        callback();
      }
    });
  }, { rootMargin: '0px 0px 200px 0px' }); // 提前200px觸發
  
  // 觀察觸發元素(通常放在列表底部)
  const trigger = document.querySelector('#loadMoreTrigger');
  observer.observe(trigger);
  
  return {
    disconnect: () => observer.disconnect()
  };
}

// 使用示例
const infiniteScroll = createInfiniteScroll(() => {
  loadMoreItems(); // 加載更多項目的函數
});

Lazy Loading 最佳實踐

性能考量

  • 1. 設定合理預加載距離,提前載入即將看到的內容
  • 2. 避免同時 Lazy Loading 太多元素
  • 3. 為無 JavaScript 環境提供備案
  • 4. 考慮網速差異,提供低質量預覽
<!-- 漸進式 Lazy Loading  -->
<img 
  src="低質量預覽.jpg" 
  data-src="高質量圖片.jpg" 
  loading="lazy" 
  class="lazy-image" 
  alt=" Lazy Loading 圖片" 
/>

<noscript>
  <!-- 無 JavaScript 的備案 -->
  <img src="高質量圖片.jpg" alt=" Lazy Loading 圖片" />
</noscript>

用戶體驗優化

  • 1. 避免載入時造成頁面跳動
  • 2. 用佔位符保留空間
  • 3. 給予適當載入反饋
  • 4. 處理快速滾動情況
/* 防止頁面跳動的CSS */
.image-container {
  position: relative;
  aspect-ratio: 16 / 9; /* 或用 padding-top 技巧 */
  background-color: #f0f0f0; /* 佔位色 */
  overflow: hidden;
}

.image-container img {
  position: absolute;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.image-container img.loaded {
  opacity: 1;
}
// 加載完成後平滑顯示
function onImageLoad(img) {
  img.classList.add('loaded');
}

// 使用低質量預覽圖技術
function loadHighQualityImage(img) {
  const highResImage = new Image();
  highResImage.src = img.dataset.src;
  highResImage.onload = () => {
    img.src = highResImage.src;
    onImageLoad(img);
  };
}

🔥 常見面試題目

(一)什麼是 Lazy Loading?它解決了什麼問題?

解答:

Lazy Loading 是延遲加載頁面中不立即需要的資源,等用戶真正需要時才加載。

它解決的問題:

  • 1. 減少初始頁面載入時間
  • 2. 降低伺服器負擔和省流量
  • 3. 在手機上效能更好
  • 4. 減少資源浪費(用戶可能永遠不會滾到某些內容)

(二)如何處理 Lazy Loading 過程中的用戶體驗問題?

解答:

Lazy Loading 的用戶體驗關鍵點:

防止頁面跳動:

  1. 1. 圖片加載前先設好固定尺寸
  2. 2. 用 CSS aspect-ratio 或 padding-top 保留空間

加載狀態提示:

  1. 1. 用 Skeleton 骨架屏
  2. 2. 加載完成後淡入顯示
  3. 3. 先顯示模糊預覽圖

處理失敗情況:

  1. 1. 設置重試機制
  2. 2. 提供友好錯誤提示
  3. 3. 準備備用內容

特殊情況優化:

  1. 1. 快速滾動時的加載優先順序
  2. 2. 預加載即將需要的內容
  3. 3. 根據網速調整加載策略