信任是好事,观察是最好的:Intersection Observer v2

Browser Support

  • Chrome: 51.
  • Edge: 15.
  • Firefox: 55.
  • Safari: 12.1.

Source

Intersection Observer 可能是最受大家喜爱的 API 之一,并且可在所有主要浏览器中使用。开发者已将此 API 用于各种应用场景,包括延迟加载图片和视频在元素到达 position: sticky 时发送通知触发分析事件等等。

从最基本的层面上讲,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,您可以了解元素何时滚动到窗口的视口中。不过,您无法确定该元素是否被其他网页内容遮盖(称为遮挡),也无法确定该元素是否因 CSS(例如 transformopacityfilter)而发生修改,这可能会导致该元素不可见。

对于顶级文档中的元素,可以通过使用 JavaScript(例如 DocumentOrShadowRoot.elementFromPoint())分析 DOM 来确定此信息。相比之下,如果相关元素位于第三方 iframe 中,则无法获得相同的信息。

为什么可见性很重要?

遗憾的是,互联网上存在不良行为者。例如,不诚实的发布商可能会在网站上使用按点击付费广告。他们可能会诱骗用户点击这些广告以赚取更多收入,至少在广告联盟发现他们的计划之前是这样。此类广告通常在 iframe 中投放。

为了欺骗用户,发布商可以使用 CSS 将广告 iframe 完全透明化:iframe { opacity: 0; }。然后,他们可以将这些透明 iframe 放置在用户想要点击的吸引人的内容(例如可爱的猫咪视频)上。这称为点击劫持

您可以在演示的上半部分中看到此类点击劫持攻击的实际效果。尝试“观看”猫咪视频,并激活技巧模式。即使您在 iframe 透明时(无意中)点击了 iframe 中的广告,系统也会将这些点击记录为合法点击。

通过将广告样式设置为透明并将其叠加在有吸引力的内容上,诱骗用户点击广告。

Intersection Observer v2 的改进

Intersection Observer v2 可以跟踪元素在人类眼中的“可见性”。如果您在 IntersectionObserver 构造函数中设置了某个选项,则生成的 IntersectionObserverEntry 实例会包含一个名为 isVisible 的新布尔值字段。当 isVisibletrue 时,浏览器会确保元素完全不被其他内容遮盖,并且没有隐藏或更改其显示的视觉效果。如果 isVisiblefalse,浏览器无法做出该保证。

该规范允许出现假负例:即使元素确实可见且未发生变化,isVisible 也可以为 false为了提高性能,浏览器会使用更简单的计算方法(例如边界框和矩形),而不会检查每个像素的复杂细节(例如 border-radius)。

不过,在任何情况下都不允许出现误报。这意味着,如果元素未完全可见且未修改,isVisible 将不会是 true

应用这些更改

IntersectionObserver 构造函数现在接受两个额外的配置属性:

  • delay 是一个数字,用于指示对于给定目标,来自观测器的通知之间的最小延迟时间(以毫秒为单位)。
  • trackVisibility 是一个布尔值,用于指示观察者是否会跟踪目标可见性的变化。

如果 trackVisibilitytrue,则 delay 必须设置为 100 或更高的值(即每 100 毫秒最多一次通知)。由于计算可见性需要大量资源,因此这是一种预防性能下降和电池消耗的措施。负责任的开发者应使用可容忍的最大延迟值。

规范,用于计算可见性。与版本 1 类似,当观察者的 trackVisibility 属性为 false 时,目标会被视为可见

在版本 2 中,如果目标满足以下条件,则会被视为不可见:

  • 它具有有效的转换矩阵,而不是 2D 平移或按比例 2D 放大。

  • 目标或其包含块链中的任何元素具有小于 1.0 的有效不透明度。

  • 目标或其包含的块链中的任何元素应用了任何过滤器。

  • 如果实现无法保证目标完全不被其他网页内容遮挡。

这意味着,当前实现在保证可见性方面相当保守。例如,应用几乎难以察觉的灰度滤镜 (filter: grayscale(0.01%)) 或设置最小的透明度 (opacity: 0.99) 会使元素不可见。

以下代码示例展示了新的 API 功能。您可以在演示的第二部分中看到点击跟踪逻辑的实际效果, 尝试“观看”小狗视频。启用欺骗模式,将自己转换为恶意者,并了解 Intersection Observer v2 如何防止跟踪非正当的广告点击。 Intersection Observer v2 可为我们提供保护。

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 v2, fallback 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 中实现该功能。