跳至主要内容

[WebAPIs] Intersection Observer API

keywords: lazy-load, infinite scrolling, scroll, parallax scrolling, monitor user viewing, 監控使用者閱讀某區塊的時間

Intersection Observer API @ MDN

TL;DR

// callback 被呼叫的次數取決於 threshold 的設定
// callback 中可以取得 entries 和 observer
var onObserveCallback = function (entries, observer) {
entries.forEach((entry) => {
// 每一個 entry 都是描述被觀察物件有 intersection change 的情況
entry.target; // 進入 entry 的 element
entry.isIntersecting; // 觀察者(viewport) 和被觀察者(target)是否有交互
entry.intersectionRatio; // 被觀察者(target)和觀察者(viewport)的重疊比例

// entry.boundingClientRect
// entry.intersectionRect
// entry.rootBounds
// entry.time
});
};

var options = {
root: document.querySelector('#scrollArea'), // 預設 document viewport
rootMargin: '0px', // 預設 0, 也可以像 CSS 設定 '10px 20px 30px 40px'
threshold: 1.0, // 預設 0, 也可以使用陣列 [0, 0.25, 0.5, 0.75, 1]
};

var observer = new IntersectionObserver(onObserveCallback, options);
  • entry.intersectionRatio:被觀察對象有多少的比例進入了 viewport 中,回傳 0 ~ 1,0 表示尚未進入 viewport,1 表示完全進入 viewport。

  • Entry.intersectionRect:被觀察對象的左上角在 viewport 的距離:

    {
    x: 34, // target 左上角的定位
    y: 718.34375, // target 左上角的定位
    width: 510, // target 進入 viewport 時的寬度
    height: 107.65625, // target 進入 viewport 時的高度
    left: 34, // 和 x 的值相同
    right: 544,
    top: 718.34375, // 和 y 的值相同
    bottom: 826,
    }

Intersection Observer API 於 2018-09-19 時在 safari 仍不支援。

說明

過去當我們想要做圖片延遲載入(image lazy-load)、無限滾動(infinite scrolling)、視差滾動(parallax scrolling)的時候,常會使用 scroll 事件來偵測使用者視窗所在的位置,同時透過 Element.getBoundingClientRect()` 來取得某一 DOM 元素的位置。然而,這些事件都是跑在主要的執行緒(main thread)上,很容易就會影響到瀏覽器的效能

透過 Intersection Observer API 可以讓註冊一個 callback function,這個函式會在「每當一個被監控的元素進入/離開某個元素或 viewport 時」,或者「兩者相交達到的一定的次數時」觸發執行,如此,網頁不需要在主執行緒來監控這些元素。

Intersection Observer API 不能告訴你的是,兩個元素之前實際上覆蓋了多少像素。但這已經足夠應付大多數的情況,你只需要知道在兩個元素大約相交幾 % 時,要做什麼就好。

Intersection observer 的概念和使用

keywords: target, root, intersection ratio

在 Intersection Observer API 中,把要監控的元素稱作 target(目標),而要比較的另一個對象(可能是裝置的 viewport 或另元素)稱作 **root element(根元素)**或 root(根),當目標和根元素之間交會時,就會觸發先前所定義好的 callback function。

習慣上,我們會監聽 document 的 viewport(只需將 root element 定義為 null),而這個 callback function 會在當 target element 的可見度(visibility)改變時執行,也就是它會 target element 和 root 相交會到一定的程度時。

Target element 和 root 兩個相交的程度稱作 intersection ratio(相交比例),它用來表示 target element 介於 0.0 ~ 1.0 之間的可見度。

root element 一定要是 target element 的上層元素(ancestor element)

建立 intersection observer 和設定 root

呼叫 intersection observer 的建構式,並且代入當**閾值(threshold)**超過一定程度時要執行的 callback function。其中 threshold 設為 1.0 表示當 target 在 root 中的可見度為 100% 時,會觸發 callback:

// callback 被呼叫的次數取決於 threshold 的設定
// callback 中可以取得 entries 和 observer
var onObserveCallback = function (entries, observer) {
entries.forEach((entry) => {
// 每一個 entry 都是描述被觀察物件有 intersection change 的情況
entry.target; // 進入 entry 的 element
entry.isIntersecting; // 觀察者(viewport) 和被觀察者(target)是否有交互
entry.intersectionRatio; // 被觀察者(target)和觀察者(viewport)的重疊比例

// entry.boundingClientRect
// entry.intersectionRect
// entry.rootBounds
// entry.time
});
};

var options = {
root: document.querySelector('#scrollArea'), // 預設 document viewport
rootMargin: '0px', // 預設 0, 也可以像 CSS 設定 '10px 20px 30px 40px'
threshold: 1.0, // 預設 0, 也可以使用陣列 [0, 0.25, 0.5, 0.75, 1]
};

var observer = new IntersectionObserver(onObserveCallback, options);
  • root:用來檢視 target element 可視度的相對元素,必須是 target 的父層以上元素預設沒定義或使用 null 時,會使用瀏覽器的 viewport 作為 root
  • rootMargin :root 周圍的 margin,可以使用和 CSS margin 屬性相似的值,例如 10px 20px 30px 40px,也可以是百分比,用來作為 root element 計算 bounding box 時增加或減少的數值,預設是 0 。
  • threshold:可以是單一數字或帶有數字的陣列,用來表示當 target 的可見度到多少時會執行 callback。如果你希望可視度超過 50% 則可以使用 0.5;如果你希望可視度每超過 25% 就執行 callback,可以使用 [0, 0.25, 0.5, 0.75, 1]。預設是 0,表示即使 target 只有 1 pixel 進入 root 也會觸發 callback;若設成 1.0 表示要整個 target 進入 root 才會觸發 callback。

rootMargin

當我們設定 rootMargin 的 bottom 時,可以讓該 target 在還沒完全進入 viewport 前就觸發 callback 執行:

/**
* 透過設定 rootMargin
* 當 target 還在 viewport 的下方 200px 時就會觸發 callback
**/

let lazyImageObserver = new IntersectionObserver(
function (entries, observer) {
// Lazy loading image code goes here ...
},
{
rootMargin: '0px 0px 200px 0px',
},
);

設定要追蹤的 target

var target = document.querySelector('#listItem');
observer.observe(target); // 開始觀察
observer.unobserve(target); // 結束觀察

每當 target 達到在 IntersectionObserver 中所指定的 threshold 時,當初定義的 callback 就會被觸發,這個 callback function 會收到一系列的  IntersectionObserverEntry 和 observer。

要留意的是,這個 callback 會在主要執行緒(main thread)執行,它應該要盡可能快的被運作,如果有任何需要耗時的東西需要被完成,可以使用 Window.requestIdleCallback()

延伸閱讀

參考