信任關係良好,觀測能力更佳:《Intersection Observer v2》

Intersection Observer 2 版除了可觀察交叉點,還能偵測交叉點時是否可見交叉元素。

Intersection Observer v1 是廣受歡迎的 API 之一,現在Safari 也支援這個 API,因此終於可以在所有主要瀏覽器中普遍使用。如要快速複習 API,建議您觀看 Surma 在下方嵌入的 Intersection Observer v1 中提供的超強 Microtip。您也可以參閱 Surma 的深入文章。使用者將 Intersection Observer v1 用於各種用途,例如圖片和影片的延遲載入在元素到達 position: sticky 時收到通知觸發分析事件等等。

如需完整詳細資料,請參閱 MDN 上的 Intersection Observer 說明文件。不過,在此簡單提醒,在最基本情況下,Intersection Observer v1 API 的運作方式如下:

const onIntersection = (entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      console.log(entry);
    }
  }
};

const observer = new IntersectionObserver(onIntersection);
observer.observe(document.querySelector('#some-target'));

Intersection Observer v1 有哪些挑戰?

明確地說,Intersection Observer v1 是不錯的做法,但並不完美。在某些極端情況下,API 可能會失效。讓我們來進一步瞭解!Intersection Observer v1 API 可告知元素何時捲動至視窗的檢視區,但不會告知元素是否遭其他網頁內容遮蓋 (也就是元素遭遮蔽),或是元素的視覺顯示是否已經由 transformopacityfilter 等視覺效果修改,因為這些視覺效果實際上會使元素無法顯示。

對於頂層文件中的元素,您可以透過 JavaScript 分析 DOM (例如透過 DocumentOrShadowRoot.elementFromPoint()),然後深入探討,來判斷這項資訊。相反地,如果問題元素位於第三方 iframe 中,就無法取得相同的資訊。

為什麼實際可視度如此重要?

所幸,網際網路已經是意圖較惡劣的不肖人士。 舉例來說,在內容網站上放送按次付費廣告的不明出版商,可能會受到誘因,以便欺騙使用者點按廣告,藉此提高廣告收益 (至少在廣告聯播網發現前,可持續一段時間)。這類廣告通常會在 iframe 中放送。如今,如果發布商想讓使用者點擊這類廣告,可以套用 CSS 規則 iframe { opacity: 0; },並在廣告 iframe 上疊加吸引人的內容,例如使用者會想點選的可愛貓咪影片。這就是所謂的「點擊劫持」。您可以在這個示範的上一節中看到這類點擊劫持攻擊的實際運作情況 (嘗試「觀看」貓咪影片,並啟用「模擬模式」)。您會發現,即使 iframe 中的廣告在您 (假裝非自願) 點按時完全透明,系統仍會「認為」廣告收到合法點擊。

將廣告設為透明,並疊加在吸引人的內容上,誘騙使用者點擊廣告。

Intersection Observer 2.0 如何修正這個問題?

Intersection Observer v2 引入了追蹤目標元素實際「可見度」的概念,就像人類會定義可見度一樣。在 IntersectionObserver 建構函式中設定選項後,交集的 IntersectionObserverEntry 執行個體就會包含名為 isVisible 的新布林欄位。isVisibletrue 值是來自基礎實作項目的強力保證,可確保目標元素不會遭其他內容完全遮蔽,且不會套用任何會變更或扭曲其螢幕顯示效果的視覺效果。相反地,false 值表示實作無法保證這點。

所謂規格的重要細節,在於實作「允許」回報偽陰性 (也就是說,即使目標元素完全顯示且未經修改,仍將 isVisible 設為 false)。基於效能或其他原因,瀏覽器會限制自行使用定界框和直線幾何圖形,無法嘗試在 border-radius 等修改的情況下達到像素完美的結果。

也就是說,在任何情況下,都不允許誤判 (也就是說,如果目標元素並未完全顯示及未經修改,則將 isVisible 設為 true)。

實際的新程式碼看起來會是什麼樣子?

IntersectionObserver 建構函式現在會採用兩個額外的設定屬性:delaytrackVisibilitydelay 是一個數字,表示觀測器針對特定目標發送通知的最短延遲時間 (以毫秒為單位)。trackVisibility 是布林值,表示觀察器是否會追蹤目標的瀏覽權限變更。

請務必注意,當 trackVisibilitytrue 時,delay 必須至少為 100 (也就是每 100 毫秒不得超過一個通知)。如先前所述,瀏覽權限的計算成本高昂,而這項規定是避免效能下降和電池耗電量的預防措施。負責的開發人員會使用最大可容許的值來設定延遲時間。

根據目前的規格,可見性的計算方式如下:

  • 如果觀測器的 trackVisibility 屬性為 false,則系統會將目標視為可見。這與目前的 v1 行為相符。

  • 如果目標具有有效的轉換矩陣,而非 2D 平移或比例 2D 向上調整,則系統會將目標視為不可見。

  • 如果目標或其所含區塊鏈中的任何元素具有 1.0 以外的有效不透明度,系統會將目標視為隱藏。

  • 如果目標或其所屬區塊鏈中的任何元素套用了任何篩選器,則系統會將目標視為不可見。

  • 如果實作無法保證目標不會完全遭其他網頁內容遮蔽,則系統會將目標視為不可見。

也就是說,目前的實作方式相當保守,可確保可見度。舉例來說,如果套用 filter: grayscale(0.01%) 這類幾乎無法察覺的灰階篩選器,或是使用 opacity: 0.99 設定幾乎看不見的透明度,都會使元素顯示為不可見。

以下是說明新 API 功能的簡短程式碼範例。您可以在示範的第二部分看到其點擊追蹤邏輯的實際運作情況 (但現在試著「觀看」小狗影片)。請務必再次啟用「模擬模式」,立即將自己轉換為可疑的發布商,並瞭解 Intersection Observer 第 2 版如何防止追蹤非正當的廣告點擊。這次,Intersection Observer v2 推出了!🎉

Intersection Observer 第 2 版可避免無意點擊廣告。

<!DOCTYPE html>
<!-- This is the ad running in the iframe -->
<button id="callToActionButton">Buy now!</button>
// This is code running in the iframe.

// The iframe must be visible for at least 800ms prior to an input event
// for the input event to be considered valid.
const minimumVisibleDuration = 800;

// Keep track of when the button transitioned to a visible state.
let visibleSince = 0;

const button = document.querySelector('#callToActionButton');
button.addEventListener('click', (event) => {
  if ((visibleSince > 0) &&
      (performance.now() - visibleSince >= minimumVisibleDuration)) {
    trackAdClick();
  } else {
    rejectAdClick();
  }
});

const observer = new IntersectionObserver((changes) => {
  for (const change of changes) {
    // ⚠️ Feature detection
    if (typeof change.isVisible === 'undefined') {
      // The browser doesn't support Intersection Observer v2, falling back to v1 behavior.
      change.isVisible = true;
    }
    if (change.isIntersecting && change.isVisible) {
      visibleSince = change.time;
    } else {
      visibleSince = 0;
    }
  }
}, {
  threshold: [1.0],
  // 🆕 Track the actual visibility of the element
  trackVisibility: true,
  // 🆕 Set a minimum delay between notifications
  delay: 100
}));

// Require that the entire iframe be visible.
observer.observe(document.querySelector('#ad'));

特別銘謝

感謝 Simeon VincentYoav WeissMathias Bynens 查看本文,也感謝 Stefan Zager 進行審查,並在 Chrome 中實作這項功能。主頁橫幅圖片由 Unsplash 上的 Sergey Semin 提供。