Intersection Observer API

Baseline 廣泛可用 *

此功能已成熟,並可在多種裝置和瀏覽器版本上執行。自 2019 年 3 月以來,它已在所有瀏覽器中可用。

* 此特性的某些部分可能存在不同級別的支援。

Intersection Observer API 提供了一種非同步觀察目標元素與祖先元素或頂級文件的視口的交集變化的方法。

概述

過去,檢測元素的可見性,或兩個元素之間相對可見性一直是一項困難的任務,其解決方案不可靠,並且容易導致瀏覽器和使用者訪問的網站執行緩慢。隨著網路的成熟,對這種資訊的需求不斷增長。需要交叉資訊的原因有很多,例如:

  • 在頁面滾動時對影像或其他內容進行懶載入。
  • 實現“無限滾動”網站,隨著您的滾動,會載入並渲染越來越多的內容,這樣使用者就不必翻頁。
  • 報告廣告可見性以計算廣告收入。
  • 根據使用者是否會看到結果來決定是否執行任務或動畫過程。

過去實現交叉檢測涉及到事件處理程式和迴圈呼叫諸如 Element.getBoundingClientRect() 之類的方法,以構建每個受影響元素所需的資訊。由於所有這些程式碼都在主執行緒上執行,即使其中一個也可能導致效能問題。當網站載入了這些測試時,情況可能會變得非常糟糕。

考慮一個使用無限滾動的網頁。它使用供應商提供的庫來管理頁面中定期放置的廣告,頁面中到處都有動畫圖形,並使用自定義庫來繪製通知框等。這些都擁有自己的交叉檢測例程,所有這些都在主執行緒上執行。網站作者甚至可能沒有意識到這種情況正在發生,因為他們可能對所使用的兩個庫的內部工作知之甚少。當用戶滾動頁面時,這些交叉檢測例程在滾動處理程式碼期間不斷觸發,導致使用者對瀏覽器、網站和他們的計算機感到沮喪。

Intersection Observer API 允許程式碼註冊一個回撥函式,該函式在特定元素進入或離開與另一個元素(或視口)的交集時,或當兩個元素之間的交集以指定量發生變化時執行。透過這種方式,網站不再需要在主執行緒上執行任何操作來監視這種元素交集,並且瀏覽器可以自由地根據需要最佳化交集的管理。

Intersection Observer API 無法做的一件事:根據重疊畫素的確切數量或具體是哪些畫素觸發邏輯。它只解決了常見的用例:“如果它們大約重疊 N%,我需要做些什麼。”

概念與用法

Intersection Observer API 允許您配置一個回撥,當發生以下任一情況時呼叫該回調:

  • 一個目標元素與裝置的視口或指定的元素相交。該指定的元素在 Intersection Observer API 中稱為根元素
  • 觀察器首次被要求觀察目標元素。

通常,您會希望觀察目標元素與其最近的可滾動祖先的交集變化,或者,如果目標元素不是可滾動元素的後代,則觀察裝置的視口。要觀察與裝置視口相關的交集,請將 root 選項指定為 null。繼續閱讀以獲取有關交集觀察器選項的更詳細解釋。

無論您使用視口還是其他元素作為根,API 的工作方式都是相同的,它會在目標元素的可見性發生變化以使其與根的交集達到所需量時執行您提供的回撥函式。

目標元素與其根之間的交集程度是交集比率。這是一個表示目標元素可見百分比的值,介於 0.0 和 1.0 之間。

建立交集觀察器

透過呼叫其建構函式並向其傳遞一個回撥函式來建立交集觀察器,該函式將在閾值向任一方向越過時執行:

js
const options = {
  root: document.querySelector("#scrollArea"),
  rootMargin: "0px",
  scrollMargin: "0px",
  threshold: 1.0,
};

const observer = new IntersectionObserver(callback, options);

閾值為 1.0 意味著當 100% 的目標在 root 選項指定的元素中可見時,將呼叫回撥。

交集觀察器選項

傳遞給 IntersectionObserver() 建構函式的 options 物件允許您控制呼叫觀察器回撥的條件。它具有以下欄位:

用作檢查目標可見性的視口的元素。必須是目標的祖先。如果未指定或為 null,則預設為瀏覽器視口。

rootMargin

根周圍的邊距。一個由一到四個值組成的字串,類似於 CSS margin 屬性,例如 "10px 20px 30px 40px"(上、右、下、左)。值只能是畫素 (px) 或百分比 (%)。這組值用於在計算交集之前增大或縮小根元素的邊界框的每一側。負值會縮小根元素的邊界框,正值會擴大它。如果未指定,預設值為 "0px 0px 0px 0px"

scrollMargin

巢狀滾動容器周圍的邊距,其值/預設值與 rootMargin 相同。在計算交集之前,邊距應用於巢狀的可滾動容器。正值會增大容器的裁剪矩形,允許目標在變得可見之前相交,而負值會縮小裁剪矩形。

threshold

單個數字或數字陣列,指示在目標可見性的哪個百分比時應執行觀察器的回撥。如果您只想檢測可見性何時超過 50% 標記,可以使用值 0.5。如果您希望在可見性每次超過另一個 25% 時執行回撥,您可以指定陣列 [0, 0.25, 0.5, 0.75, 1]。預設值為 0(表示即使有一個畫素可見,也將執行回撥)。值 1.0 意味著直到每個畫素都可見才認為閾值已透過。

delay 實驗性

當跟蹤目標可見性(trackVisibilitytrue)時,這可用於設定此觀察器通知之間的最小延遲(以毫秒為單位)。限制通知速率是可取的,因為可見性計算是計算密集型的。如果跟蹤可見性,對於任何小於 100 的值,該值將設定為 100,並且您應該使用最大可容忍值。該值預設為 0。

trackVisibility 實驗性

一個布林值,指示此 IntersectionObserver 是否正在跟蹤目標可見性的變化。

當為 false 時,當目標元素滾動到根元素的視口時,瀏覽器將報告交集。當為 true 時,瀏覽器將額外檢查目標是否實際可見,並且沒有被其他元素覆蓋,或者可能被濾鏡、降低的不透明度或某些變換扭曲或隱藏。預設值為 false,因為跟蹤可見性是計算密集型的。如果設定此項,也應設定一個delay

交集變化回撥

傳遞給 IntersectionObserver() 建構函式的回撥接收 IntersectionObserverEntry 物件的列表和觀察器:

js
const callback = (entries, observer) => {
  entries.forEach((entry) => {
    // Each entry describes an intersection change for one observed
    // target element:
    //   entry.boundingClientRect
    //   entry.intersectionRatio
    //   entry.intersectionRect
    //   entry.isIntersecting
    //   entry.rootBounds
    //   entry.target
    //   entry.time
  });
};

回撥接收到的條目列表包含每個閾值交叉事件的一個 IntersectionObserverEntry 物件——可以同時接收多個條目,無論是來自多個目標還是來自單個目標在短時間內交叉多個閾值。條目使用佇列分派,因此它們應該按生成時間排序,但您最好使用 IntersectionObserverEntry.time 正確排序它們。每個條目都描述了給定元素與根元素的交集程度,元素是否被認為正在相交,等等。該條目只包含有關該特定瞬間的資訊——如果您想要需要隨時間跟蹤的資訊,例如滾動方向和速度,您可能需要透過記住以前收到的條目自行計算。

請注意,您的回撥在主執行緒上執行。它應該儘可能快地執行;如果需要做任何耗時的事情,請使用 Window.requestIdleCallback()

下面的程式碼片段顯示了一個回撥,該回調記錄了元素從不與根相交到至少 75% 相交的次數。對於閾值 0.0(預設值),回撥大約在 isIntersecting 的布林值轉換時呼叫。因此,該片段首先檢查轉換是否為正,然後確定 intersectionRatio 是否高於 75%,在這種情況下它會增加計數器。

js
const intersectionCallback = (entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      let elem = entry.target;

      if (entry.intersectionRatio >= 0.75) {
        intersectionCounter++;
      }
    }
  });
};

指定要觀察的元素

建立觀察器後,您需要為其指定一個要觀察的目標元素:

js
const target = document.querySelector("#listItem");
observer.observe(target);

// the callback we set up for the observer will be executed now for the first time
// it waits until we assign a target to our observer (even if the target is currently not visible)

每當目標達到為 IntersectionObserver 指定的閾值時,都會呼叫回撥。

此外,請注意,如果您指定了 root 選項,則目標必須是根元素的後代。

如何計算交集

Intersection Observer API 考慮的所有區域都是矩形;不規則形狀的元素被認為佔據了包含元素所有部分的最 小矩形。同樣,如果元素的可見部分不是矩形,則元素的交集矩形被認為是包含元素所有可見部分的最 小矩形。

瞭解 IntersectionObserverEntry 提供的各種屬性如何描述交集非常有用。

交集根和根邊距

在我們可以跟蹤元素與容器的交集之前,我們需要知道容器是什麼。該容器是交集根根元素。這可以是文件中觀察元素的祖先的特定元素,也可以是 null 以使用文件的視口作為容器。

根交集矩形是用於與目標進行檢查的矩形。該矩形是這樣確定的:

  • 如果交集根是隱式根(即頂級 Document),則根交集矩形是視口的矩形。
  • 如果交集根具有溢位剪裁,則根交集矩形是根元素的內容區域。
  • 否則,根交集矩形是交集根的邊界客戶端矩形(透過呼叫其上的 getBoundingClientRect() 返回)。

透過在建立 IntersectionObserver 時設定根邊距 rootMargin,可以進一步調整根交集矩形。rootMargin 中的值定義了新增到交集根邊界框每一側的偏移量,以建立最終的交集根邊界(在執行回撥時在 IntersectionObserverEntry.rootBounds 中公開)。正值會增大框,而負值會縮小框。每個偏移值只能以畫素 (px) 或百分比 (%) 表示。

使用根邊距增大框的效果是允許溢位目標在變得可見之前與根相交。這可以用於,例如,在影像進入檢視之前就開始載入它們,而不是在它們變得可見時才載入。

在下面的示例中,我們有一個可滾動框和一個最初不可見的元素。您可以調整根右邊距,並看到:

  • 如果邊距為正,即使紅色元素不可見,它也被認為與根相交,因為它與根的邊距區域相交。
  • 如果邊距為負,那麼即使紅色元素開始變得可見,它仍然不被認為與根相交,因為根的邊界框已縮小。

交集根和滾動邊距

考慮您有一個根元素包含巢狀的滾動容器的情況,並且您想要觀察與其中一個可滾動容器內的目標的交集。預設情況下,當目標在其根定義的區域內可見時,與目標元素的交集開始變得可觀察;換句話說,當容器滾動到根中並目標滾動到其容器的裁剪矩形中時。

您可以使用滾動邊距在目標滾動到其滾動容器內檢視之前或之後開始觀察交集。邊距被新增到根中的所有巢狀滾動容器,包括根元素(如果它也是滾動容器),並且具有增大(正邊距)或縮小(負邊距)用於計算交集的裁剪區域的效果。

注意:您可以在每個您想要滾動邊距的滾動容器上建立一個交集觀察器,並使用根邊距屬性實現類似的效果。使用滾動邊距更符合人體工程學,因為在大多數情況下,您只需為所有巢狀目標設定一個交集觀察器。

在下面的示例中,我們有一個可滾動框和一個最初不可見的影像輪播。根元素上的觀察器觀察輪播內的影像元素目標。當影像元素開始與根元素相交時,影像被載入,交集被記錄,並且觀察器被移除。

向下滾動以顯示輪播。可見影像應立即載入。如果您滾動輪播,您應該觀察到影像在元素變得可見時立即載入。

重置示例後,您可以使用提供的控制元件更改滾動邊距百分比。如果您設定一個正值(例如 20%),則滾動容器的剪下矩形將增加 20%,您應該觀察到影像在進入檢視之前就被檢測並載入。同樣,負值將意味著一旦影像已在檢視中,就會檢測到交集。

閾值

Intersection Observer API 不會報告目標元素可見性的每個微小變化,而是使用閾值。建立觀察器時,您可以提供一個或多個數字值,表示目標元素的可見百分比。然後,API 只報告跨越這些閾值的可見性變化。

例如,如果您想在目標可見性每次向前或向後透過每個 25% 標記時收到通知,您將在建立觀察器時將陣列 [0, 0.25, 0.5, 0.75, 1] 指定為閾值列表。

呼叫回撥時,它會收到一個 IntersectionObserverEntry 物件列表,每個觀察到的目標一個,其與根的交集程度發生變化,使得暴露量跨越其中一個閾值,無論方向如何。

您可以透過檢視條目的 isIntersecting 屬性來檢視目標當前是否與根相交;如果其值為 true,則目標至少部分與根元素或文件相交。這使您可以確定該條目表示元素從相交到不再相交的轉換,還是從不相交到相交的轉換。

請注意,可能存在零交集矩形,這可能發生在交集恰好沿兩者之間的邊界或 boundingClientRect 面積為零的情況下。目標和根共享邊界線的這種狀態不足以被認為是過渡到相交狀態。

為了瞭解閾值的工作原理,請嘗試滾動下面的框。其中每個彩色框都顯示了自身在所有四個角中可見的百分比,因此您可以在滾動容器時看到這些比率隨時間變化。每個框都有一組不同的閾值:

  • 第一個框的每個可見百分點都有一個閾值;也就是說,IntersectionObserver.thresholds 陣列是 [0.00, 0.01, 0.02, /*…,*/ 0.99, 1.00]
  • 第二個框只有一個閾值,在 50% 標記處。
  • 第三個框每 10% 的可見度(0%、10%、20% 等)都有閾值。
  • 最後一個框每 25% 有閾值。

跟蹤可見性和延遲

預設情況下,當目標元素滾動到根元素的視口時,觀察器會提供通知。雖然在許多情況下只需要這樣做,但有時在目標“視覺上受損”時,不報告交集很重要。例如,在測量分析或廣告展示時,目標元素沒有被隱藏或扭曲(無論是全部還是部分)很重要。

trackVisibility 設定會告訴觀察器僅報告瀏覽器不認為視覺上受損的目標的交集,例如透過改變不透明度或應用濾鏡或變換。該演算法是保守的,可能會省略技術上可見的元素,例如那些只有輕微不透明度降低的元素。

可見性計算是計算密集型的,只有在必要時才應使用。當跟蹤可見性時,還應設定 delay 以限制最小報告週期。建議您將延遲設定為最大可容忍值(跟蹤可見性時的最小延遲為 100 毫秒)。

剪裁和交集矩形

瀏覽器按如下方式計算最終的交集矩形;所有這些都是為您完成的,但瞭解這些步驟有助於更好地理解何時會發生交集。

  1. 透過呼叫目標上的 getBoundingClientRect() 獲取目標元素的邊界矩形(即完全包圍構成元素的所有元件的邊界框的最小矩形)。這是交集矩形可能的最大值。其餘步驟將移除任何不相交的部分。
  2. 從目標的直接父塊開始並向外移動,將每個包含塊的剪裁(如果有)應用於交集矩形。塊的剪裁是根據兩個塊的交集和 overflow 屬性指定的剪裁模式(如果有)確定的。將 overflow 設定為除 visible 以外的任何值都會導致發生剪裁。
  3. 如果其中一個包含元素是巢狀瀏覽上下文的根(例如包含在 <iframe> 中的文件),則交集矩形被剪裁到包含上下文的視口,並且向上透過容器的遞迴繼續使用容器的包含塊。因此,如果達到 <iframe> 的頂層,則交集矩形被剪裁到框架的視口,然後框架的父元素是下一個向交集根遞迴的塊。
  4. 當向上遞迴到達交集根時,將結果矩形對映到交集根的座標空間。
  5. 然後透過將其與根交集矩形相交來更新結果矩形。
  6. 最後,將此矩形對映到目標的 document 的座標空間。

介面

IntersectionObserver

Intersection Observer API 的主要介面。提供用於建立和管理觀察器的方法,該觀察器可以觀察任意數量的目標元素以獲取相同的交集配置。每個觀察器都可以非同步觀察一個或多個目標元素與其共享祖先元素或其頂級 Document視口 之間的交集變化。祖先或視口被稱為

IntersectionObserverEntry

描述目標元素及其根容器在特定過渡時刻的交集。此型別的物件只能透過兩種方式獲得:作為 IntersectionObserver 回撥的輸入,或透過呼叫 IntersectionObserver.takeRecords()

一個簡單的例子

這個簡單的例子使目標元素在變得或多或少可見時改變其顏色和透明度。在 使用 Intersection Observer API 測量元素可見性時間中,您可以找到一個更全面的示例,展示如何測量一組元素(例如廣告)對使用者可見的時間,並透過記錄統計資料或更新元素來響應這些資訊。

HTML

本示例的 HTML 非常簡短,其中包含一個主要元素,它是我們將要定位的框(其創意 ID 為 "box"),以及框內的一些內容。

html
<div id="box">
  <div class="vertical">Welcome to <strong>The Box!</strong></div>
</div>

CSS

對於本示例的目的,CSS 並不是特別重要;它佈局元素並確定 background-colorborder 屬性可以參與 CSS 過渡,我們將使用這些過渡來影響元素隨著其可見性變化而發生的變化。

css
#box {
  background-color: rgb(40 40 190 / 100%);
  border: 4px solid rgb(20 20 120);
  transition:
    background-color 1s,
    border 1s;
  width: 350px;
  height: 350px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.vertical {
  color: white;
  font: 32px "Arial";
}

.extra {
  width: 350px;
  height: 350px;
  margin-top: 10px;
  border: 4px solid rgb(20 20 120);
  text-align: center;
  padding: 20px;
}

JavaScript

最後,讓我們看看使用 Intersection Observer API 實現功能的 JavaScript 程式碼。

設定

首先,我們需要準備一些變數並安裝觀察器。

js
const numSteps = 20.0;

const boxElement = document.querySelector("#box");
let prevRatio = 0.0;
let increasingColor = "rgb(40 40 190 / ratio)";
let decreasingColor = "rgb(190 40 40 / ratio)";

createObserver();

我們在此處設定的常量和變數是:

numSteps

一個常量,指示我們希望在可見性比率為 0.0 和 1.0 之間有多少個閾值。

prevRatio

此變數將用於記錄上次跨越閾值時的可見性比率;這將使我們能夠判斷目標元素是變得更可見還是更不可見。

increasingColor

一個字串,定義當可見性比率增加時我們將應用於目標元素的顏色。此字串中的單詞“ratio”將被替換為目標的當前可見性比率,以便元素不僅改變顏色,而且在變得不那麼模糊時變得越來越不透明。

decreasingColor

同樣,這是一個字串,定義當可見性比率降低時我們將應用的顏色。

我們使用 querySelector() 獲取 ID 為 "box" 的元素的引用,然後呼叫我們稍後將建立的 createObserver() 方法來處理構建和安裝交集觀察器。

建立交集觀察器

頁面載入完成後,會呼叫 createObserver() 方法來實際建立新的 IntersectionObserver 並開始觀察目標元素的過程。

js
function createObserver() {
  const options = {
    root: null,
    rootMargin: "0px",
    threshold: buildThresholdList(),
  };

  const observer = new IntersectionObserver(handleIntersect, options);
  observer.observe(boxElement);
}

這首先設定了一個包含觀察器設定的 options 物件。我們希望觀察目標元素相對於文件視口的可見性變化,因此 rootnull。我們不需要任何邊距,因此邊距偏移 rootMargin 指定為“0px”。這使得觀察器觀察目標元素的邊界與視口邊界之間的交集變化,沒有任何額外的(或減去的)空間。

可見性比率閾值列表 threshold 由函式 buildThresholdList() 構建。在此示例中,閾值列表是程式化構建的,因為它們數量眾多且數量可調。

一旦 options 準備就緒,我們就會建立新的觀察器,呼叫 IntersectionObserver() 建構函式,指定在交集跨越我們的一個閾值時要呼叫的函式 handleIntersect(),以及我們的一組選項。然後我們在返回的觀察器上呼叫 observe(),並將所需的目標元素傳遞給它。

如果需要,我們可以透過為每個元素呼叫 observer.observe() 來監視多個元素相對於視口的可見性交集變化。

構建閾值比率陣列

構建閾值列表的 buildThresholdList() 函式如下所示:

js
function buildThresholdList() {
  const thresholds = [];
  const numSteps = 20;

  for (let i = 1.0; i <= numSteps; i++) {
    const ratio = i / numSteps;
    thresholds.push(ratio);
  }

  thresholds.push(0);
  return thresholds;
}

它透過將值 i/numSteps 推入 thresholds 陣列來構建閾值陣列——每個閾值都是 0.0 到 1.0 之間的比率,其中 i 是 1 到 numSteps 之間的每個整數。它還推入 0 以包含該值。結果,給定 numSteps 的預設值 (20),是以下閾值列表:

# 比率 # 比率
0 0.05 11 0.6
1 0.1 12 0.65
2 0.15 13 0.7
3 0.2 14 0.75
4 0.25 15 0.8
5 0.3 16 0.85
6 0.35 17 0.9
7 0.4 18 0.95
8 0.45 19 1
9 0.5 20 0
10 0.55

當然,我們可以將閾值陣列硬編碼到我們的程式碼中,通常您最終會這樣做。但是這個示例為新增配置控制元件以調整粒度留下了空間,例如。

處理交集變化

當瀏覽器檢測到目標元素(在我們的例子中是 ID 為 "box" 的元素)被揭示或遮蔽,使得其可見性比率跨越我們列表中的一個閾值時,它會呼叫我們的處理函式 handleIntersect()

js
function handleIntersect(entries, observer) {
  entries.forEach((entry) => {
    if (entry.intersectionRatio > prevRatio) {
      entry.target.style.backgroundColor = increasingColor.replace(
        "ratio",
        entry.intersectionRatio,
      );
    } else {
      entry.target.style.backgroundColor = decreasingColor.replace(
        "ratio",
        entry.intersectionRatio,
      );
    }

    prevRatio = entry.intersectionRatio;
  });
}

對於列表 entries 中的每個 IntersectionObserverEntry,我們檢視條目的 intersectionRatio 是否正在上升;如果是,我們將目標的 background-color 設定為 increasingColor 中的字串(請記住,它是 "rgb(40 40 190 / ratio)"),並將單詞“ratio”替換為條目的 intersectionRatio。結果:顏色不僅會改變,目標元素的透明度也會改變;隨著交集比率的降低,背景顏色的 alpha 值也隨之降低,從而導致元素變得更透明。

同樣,如果 intersectionRatio 正在下降,我們使用字串 decreasingColor,並在設定目標元素的 background-color 之前將該字串中的單詞“ratio”替換為 intersectionRatio

最後,為了跟蹤交集比率是上升還是下降,我們將當前比率儲存在變數 prevRatio 中。

結果

下面是生成的內容。上下滾動此頁面,並注意在您操作時框的外觀如何變化。

使用 Intersection Observer API 測量元素可見性時間中有一個更全面的示例。

規範

規範
交集觀察器
# intersection-observer-interface

瀏覽器相容性

另見