JavaScript Intersection Observer 交叉觀察器

在過去,要判斷一個網頁元素是否出現在使用者的螢幕視窗(Viewport)內,通常需要監聽 scroll 事件並頻繁調用 getBoundingClientRect()。這種做法不僅耗費性能,還容易造成頁面捲動卡頓(Jank)。

Intersection Observer API 提供了一種更高效、非同步的方式來監測一個目標元素是否進入或離開了另一個元素(通常是瀏覽器視窗)。

為什麼要使用 Intersection Observer?

  1. 效能卓越: 觀察動作是在瀏覽器後台異步執行的,不會阻塞主執行緒。
  2. 減少計算: 瀏覽器會優化觸發時機,只有在「交叉狀態」發生變化時才執行回呼函式。
  3. 語法簡潔: 代替了複雜的偏移量計算邏輯。

基本語法

建立一個觀察器需要兩個部分:回呼函式 (callback)設定選項 (options)

// 1. 設定選項
const options = {
  root: null, // 預設為 null,代表瀏覽器視窗 (viewport)
  rootMargin: '0px', // 延伸或收縮 root 的邊界 (類似 CSS margin)
  threshold: 0.1, // 觸發門檻,0.1 代表元素出現 10% 時觸發
};

// 2. 建立回呼函式
const callback = (entries, observer) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      console.log('元素進入視窗!', entry.target);
      // 可以在這裡停止觀察該元素
      // observer.unobserve(entry.target);
    }
  });
};

// 3. 實例化觀察器並開始觀察
const observer = new IntersectionObserver(callback, options);
const target = document.querySelector('#targetElement');
observer.observe(target);

核心設定參數說明

  • root: 指定用來觀察的容器元素。如果不指定,則預設為瀏覽器的視窗。
  • rootMargin: 可以用來提前觸發事件。例如設定為 '200px',則元素距離進入視窗還有 200px 時就會被視為「已進入」。這對圖片預載非常有幫助。
  • threshold: 一個數字或陣列。設定為 [0, 0.5, 1] 代表元素在出現 0%、50%、100% 時都會各觸發一次。

實戰範例:圖片懶加載 (Lazy Loading)

這是 Intersection Observer 最常見的用途。我們可以先將真實圖片路徑存在 data-src 屬性中,等圖片出現在螢幕內時再放入 src

const lazyImages = document.querySelectorAll('img.lazy');

const imageObserver = new IntersectionObserver(
  (entries, observer) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        const img = entry.target;
        img.src = img.dataset.src; // 將暫存路徑填入真實 src
        img.classList.remove('lazy');
        observer.unobserve(img); // 載入後取消觀察,節省資源
      }
    });
  },
  { rootMargin: '100px' }
); // 提早 100px 載入,讓使用者感覺不到在載入

lazyImages.forEach((img) => imageObserver.observe(img));

實戰範例:無限捲動 (Infinite Scroll)

在列表的最下方放一個小小的偵測元素(例如 ID 為 sentinel 的 div),當它進入視窗時就向後端抓取新資料。

const sentinel = document.querySelector('#sentinel');

const scrollObserver = new IntersectionObserver((entries) => {
  if (entries[0].isIntersecting) {
    loadMoreContent(); // 執行載入更多內容的函式
  }
});

scrollObserver.observe(sentinel);

注意事項

  • 瀏覽器支援度: 現代瀏覽器(Chrome, Firefox, Safari, Edge)皆完整支援。若需支援極舊版瀏覽器,可使用 W3C 提供的 Polyfill。
  • 停止觀察: 為了維持最佳性能,當目標元素的任務完成(如圖片已載入)後,務必調用 unobserve(element)disconnect()

透過 Intersection Observer,你可以用極低的效能代價實現流暢、現代化的使用者互動介面。

相關閱讀: