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

Intersection Observer v2 不僅新增功能,讓您不單就能觀察到交錯,還能偵測交集元素是否在交集時可見。

Intersection Observer v1 是大家普遍都喜歡的 API 之一,現在 Safari 也支援這項功能,使用者終於能在所有主要瀏覽器中使用這項 API。如要快速複習這個 API,建議您在下方內嵌的 Intersection Observer v1 上,觀看 SurmaSupercharged 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 第 1 版有什麼挑戰?

明確來說,Intersection Observer v1 是很好的事,但不是完美的,某些極端情況會因為 API 沒用。讓我們一探究竟! Intersection Observer v1 API 可以告訴您元素何時捲動至視窗的可視區域,但「無法」指出該元素是否被任何其他網頁內容所覆蓋 (即該元素遮蔽時),或者元素的視覺顯示是否遭到 transformopacityfilter 等視覺效果修改。

對於頂層文件中的元素,您可以經由 JavaScript 分析 DOM 以確定這項資訊,例如透過 DocumentOrShadowRoot.elementFromPoint() 分析,再深入探究。相較之下,如果相關元素位於第三方 iframe 中,也無法取得相同的資訊。

為什麼真正的曝光率這麼高?

很不幸的是,網際網路是吸引惡意行為人,但意圖更惡劣的環境。舉例來說,在內容網站上放送每次點擊付費廣告的信譽不佳發布商可能會誘騙使用者點按廣告,提高發布商的廣告付款金額 (至少要有一段時間,直到廣告聯播網收到為止)。這類廣告通常會透過 iframe 放送。 現在,如果發布商想吸引使用者點選這類廣告,可以套用 CSS 規則 iframe { opacity: 0; },並將 iframe 覆蓋在吸睛項目上 (例如使用者實際可能會想點選的可愛貓咪影片),讓廣告 iframe 完全透明化。這就是所謂的「點擊劫持」。您可以查看本示範中上方的部分,瞭解這類點擊劫持攻擊的動作 (請嘗試「觀看」貓咪影片,然後啟用「技巧模式」)。您會發現,iFrame 中的廣告會「認為」廣告獲得有效點擊,即使在您 (自願性) 點擊時,廣告看起來完全透明也一樣。

為廣告設定透明樣式,並疊放在吸睛的廣告上,藉此誘騙使用者點擊廣告。

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

Intersection Observer v2 推出了追蹤目標元素實際「瀏覽權限」的概念,就像人類會負責定義該元素一樣。透過在 IntersectionObserver 建構函式中設定選項,與 IntersectionObserverEntry 執行個體相交之後,就會包含名為 isVisible 的新布林值欄位。isVisibletrue 值可明確保證從基礎實作中,目標元素完全不會被其他內容遮住,並且不會套用任何視覺效果改變或扭曲其顯示畫面的視覺效果。相對地,false 值表示實作無法保證一定能取得保證。

這個spec的一項重要詳細資料是,實作「允許」回報「偽陰性」 (也就是說,即使目標元素已完全顯示且未經修改,仍「允許」將 isVisible 設為 false。基於效能或其他原因,瀏覽器本身會限制使用定界框和直線幾何圖形,不會嘗試因 border-radius 等修改而獲得完美像素的結果。

換句話說,在任何情況下都不允許偽陽性 (也就是說,當目標元素無法完全顯示且未經修改時,將 isVisible 設為 true)。

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

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

請務必注意,當 trackVisibilitytrue 時,delay 必須至少為 100 (也就是每 100 毫秒不超過一則通知)。如先前所述,系統計算瀏覽權限會耗用大量資源,這項規定是預防效能降低和電池耗電的預防措施。負責任的開發人員將使用最大的容忍值做為延遲。

根據目前的spec,瀏覽權限的計算方式如下:

  • 如果觀察器的 trackVisibility 屬性為 false,則系統會將目標視為可見。這與目前的第 1 版行為相關。

  • 如果目標具有 2D 平移或 2D 比例縮放以外的有效轉換矩陣,該目標就會視為不可見。

  • 如果目標或其內含區塊鏈中的任何元素,具有 1.0 以外的有效不透明度,則目標將視為不可見。

  • 如果目標或其內含區塊鏈中的任何元素已套用任何篩選器,則目標會視為隱藏。

  • 如果實作程序無法確保目標與其他網頁內容完全無關,即可將目標視為不可見。

這表示目前的導入作業會相當保守,且能夠保證資料可見度。舉例來說,套用 filter: grayscale(0.01%) 這類幾乎不明顯的灰階篩選器,或使用 opacity: 0.99 設定幾乎不可見的透明度,所有元素都無法顯示。

以下提供簡短程式碼範例,說明新的 API 功能。您可以查看示範的第二節中實際運作的點擊追蹤邏輯 (但現在不妨試著「觀看」小狗影片)。請務必再次啟用「誘導模式」,以便立即將自己轉換為惡意的發布商,並瞭解 Intersection Observer 第 2 版如何避免追蹤不實廣告點擊。這次的 Intersection Observer 第 2 版正式推出!🎉

Intersection Observer v2 防止廣告意外點擊。

<!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 中實作這項功能。Sergey Semin 在 Unsplash 上提供的主頁橫幅。