- 閱讀時間 7 分鐘

使用 Intersection Observer API 來追蹤元素的能見度

Javascript
Markdown logo

Javascript 中的 Intersection Observer API 允許在頁面或容器中追蹤元素的能見度。

問題

瀏覽網頁時,我們會滾動其內容(通常是垂直方向的)。

螢幕右側有捲動條,讓我們追蹤頁面中的瀏覽進度。

網頁中,如果想要追蹤某些元素的能見度,可透過監聽 scroll 事件來對頁面的捲動做出反應:

window.addEventListener("scroll", () => {
const scrollPosition = window.scrollY;
// 根據位置執行操作,
// 根據能見度執行相關操作
});

這種方法可行,但有什麼問題?

  • 事件監聽函式會在每一次滾動時不斷重複觸發,可能會讓主執行緒過載
  • 為了判斷頁面上的元素是否可見,要先獲取其位置(例如使用 getBoundingClientRect()),再將其與頁面中的滾動位置比較,因此需要手動計算
  • 如果想要判斷頁面可滾動容器中的元素是否可見,基於父元素的位置,也需要手動計算

Intersection Observer

Intersection Observer API 能自然地解決這些問題。

根元素為基準,我們可在一個已定義的交集區觀察元素。

當任何被觀察的元素進入離開交集區時,可以執行回呼函式。

根元素

建立 IntersectionObserver 實例時,可以傳遞選項。

選項中,可以指定根元素:root。如果未指定,預設則為整個頁面;否則,它可以是任何可滾動的容器,用來監測某些元素的能見度。

const observer = new IntersectionObserver(
callback, // 回呼函式
{
// 根元素
root: document.getElementById("container"),
},
);

回呼函式

建立 IntersectionObserver 實例時,儲存回呼函式。

此函式會在以下兩種情況下執行:

  • 實例建立時
  • 被觀察的元素進入或離開交集區時

它需要兩個參數:進入或離開交集區的元素,以及 IntersectionObserver 的實例:

const observer = new IntersectionObserver(
(entries, observer) => {},
// 選項...
);

entries 中有一個元素陣列,每個元素都是 IntersectionObserverEntry 類型;我們可循環遍歷此陣列,並從中讀取有用資訊,例如:

  • isIntersecting, 是最方便的資訊:元素是否在交集區裡?
  • intersectionRatio, 可能會有用:元素與根元素的交叉比例是多少?

觀察元素

觀察元素很簡單。建立 IntersectionObserver 實例後,呼叫 observe 函式並傳遞要觀察的元素:

observer.observe(element);

然後,當元素進入或離開交集區時,回呼函式將被觸發。

交集區

預設情況下,交集區會覆蓋整個根元素。當被觀察的元素接觸到這個區域時,它將被視為能見。

const observer = new IntersectionObserver(callback);
Bloc 1non visible
Bloc 2non visible
Bloc 3non visible
Bloc 4non visible
Bloc 5non visible
Bloc 6non visible

以上範例中,當元素進入區域時,將被視為能見。因此邏輯上來說,會讓人覺得所有元素似乎一直都是能見的。

rootMargin

可以透過傳遞選項來使交集區更大或更小:rootMargin

rootMargin 選項接受與 CSS margin 屬性相同的語法:top right bottom left。因此可指定一到四個數值,以像素、百分比或以上兩者為單位。

讓我們看看一個使用 rootMargin-100px 的範例,這會在四個方向上將交集區各縮小100像素:

const observer = new IntersectionObserver(callback, {
rootMargin: "-100px",
});
INTERSECTION
Bloc 1non visible
Bloc 2non visible
Bloc 3non visible
Bloc 4non visible
Bloc 5non visible
Bloc 6non visible

嘗試使用以下捲動條。要縮小交集區,因此會套用負數值:

-90px
INTERSECTION
Bloc 1non visible
Bloc 2non visible
Bloc 3non visible
Bloc 4non visible
Bloc 5non visible
Bloc 6non visible

threshold

threshold 選項允許添加閾值,從而將元素視為能見。數值可用多種格式化:

  • 01 之間的數值等同於 0%100% 之間的百分比。例如若設定傳遞 0.8,則當元素至少有 80% 內容能見時,就將被視為能見。

使用 0.8 的例子:

const observer = new IntersectionObserver(callback, {
threshold: 0.8,
});
Bloc 1non visible
Bloc 2non visible
Bloc 3non visible
Bloc 4non visible
Bloc 5non visible
Bloc 6non visible
  • 使用一個 01 之間的數值陣列,用來指定 IntersectionObserver 在哪些能見百分比時需觸發回呼函式。

例如:每 10% 觸發一次函式,並顯示元素的能見百分比(intersectionRatio):

const observer = new IntersectionObserver(callback, {
threshold: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1],
});
Bloc 1non visible
Bloc 2non visible
Bloc 3non visible
Bloc 4non visible
Bloc 5non visible
Bloc 6non visible
Output :

管理表目錄

Intersection Observer API 使用的一個例子是管理表目錄。

閱讀文章時,目前閱讀的章節會被用高亮突出顯示。

這就是 Astro Starlight 的執行方式:當某個章節標題進入指定的交集區時,對應的表目錄元素將被高亮突出顯示。

Astro Starlight 包含一個 web component。此組件包含 IntersectionObserver 實例的建立

在此組件中,也能看到使用 rootMargin 選項計算交集區的方式:獲取導航欄的高度,添加行動裝置上表目錄的高度,作為交集區的起點;再往下 53 像素,則是該區的底部。當某個標題穿越這個區域時,對應的表目錄元素就將改變其外觀。

實際上 Astro Starlight 定義了兩個 web components:一個用於 行動裝置(顯示在螢幕頂部),另一個用於較大螢幕(顯示在右側)。