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

Intersection Observer v2 不僅新增可逐一觀察交叉點的功能,還能 偵測交集時是否可見交集元素。

Intersection Observer 1.0 版是可能受到大眾喜愛的 API 之一 Safari 支援 此 API 終於適用於所有主要瀏覽器如果需要快速複習 API 我推薦觀看Surma的 交集的Supercharged Microtip 在下方嵌入的 Observer v1。 此外,你也能閱讀《Surma》的深入解說 文章。 使用者將 Intersection Observer v1 用於多種用途,例如 延遲載入圖片和影片 在元素達到 position: sticky 時接收通知 觸發 Analytics 事件 以及其他工具

如需完整詳細資料,請參閱 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 可以指出哪些元素已捲動至 但它「不會」指出該元素是否被覆蓋 或是否由其他網頁內容 (也就是元素遭到遮蔽) 或 元素的視覺顯示已經過 transformopacity 等視覺效果修改 filter 等,「有效」會將其隱藏。

對於頂層文件中的元素,系統可以分析文字來判定這項資訊 透過 JavaScript 建立的 DOM,例如 DocumentOrShadowRoot.elementFromPoint()敬上 然後再進一步研究 相反地,如果相關元素 位於第三方 iframe 中

為什麼實際曝光率這麼高?

可惜的是,網際網路已經吸引了意圖更惡劣的惡意行為人。 舉例來說,如果某個可疑的發布商在內容網站上放送每次點擊付費廣告,就可能獲得利誘 以誘騙使用者點擊其廣告,以提高發布商的廣告支付金額 (至少 短暫的空檔,直到廣告聯播網成功擷取到這些流量為止)。 這類廣告通常會透過 iframe 放送。 現在,如果發布商想吸引使用者點擊這類廣告,可以將廣告設為 iframe 套用 CSS 規則 iframe { opacity: 0; } 並重疊 iframe,以完全透明的方式 再加上有吸引力的內容 例如使用者可能願意點閱的可愛貓咪影片 這就是所謂的「點擊劫持」。 您可以在本頁面上方的章節看到此次點擊劫持攻擊實際運作 示範 (嘗試「觀看」該貓咪的影片 並啟用「模擬模式」)。 您會注意到 iframe 中的廣告廣告也獲得正常點擊, 而且完全透明。

將廣告樣式設為「透明」,並疊加在引人注目的廣告內容上,藉此誘導使用者按下廣告。

Intersection Observer v2 如何解決這個問題?

Intersection Observer 第 2 版介紹了追蹤實際「可視性」的概念的目標 視為由人定義 只要在 IntersectionObserver 建構函式、 交集 IntersectionObserverEntry。 執行個體則會包含名為 isVisible 的新布林值欄位。 isVisibletrue 值是基礎實作所提供的強大保證 其他內容完全遮住目標元素 且未套用任何會改變或扭曲顯示畫面的視覺效果。 相反地,false 值則代表實作項目無法保證。

另外要介紹 規格 的導入方式「可以」回報偽陰性 (也就是說,將 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 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 中查看及實作這項功能。 主頁橫幅由 Sergey Semin 提供 Unsplash 網站上。