信任有益,观察更好:Intersection Observer v2

Intersection Observer v2 新增了观察交叉路口本身的功能, 检测相交元素在相交时是否可见。

Intersection Observer v1 是广受欢迎的 API 之一, Safari 也支持这项功能 它最终也在所有主流浏览器中普及要快速温习 API 我推荐你观看 Surma 的 交叉路口上的超能微小提示 下面嵌入的 Observer v1。 你还可以阅读 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 非常棒,但并不完美。还有 下面我们更详细地了解一下! Intersection Observer v1 API 可在元素滚动到 但并不能告知您元素是否被覆盖 被任何其他网页内容(即元素被遮挡)或广告是否被覆盖时 元素的视觉显示效果已被 transformopacityfilter 等,可有效使其不可见。

对于顶级文档中的某个元素,可通过分析 通过 JavaScript 访问 DOM,例如 DocumentOrShadowRoot.elementFromPoint() 然后再进行更深入的挖掘。 相反,如果相关元素是 位于第三方 iframe 中。

为什么实际曝光度如此重要?

不幸的是,互联网会吸引意图不端的不法分子。 例如,对于在内容网站上投放按点击付费广告的不正当发布商,我们可能会通过其他计划获得激励 诱骗用户点击他们的广告,以增加发布商的广告收益(至少 直到广告网络发现这些错误)。 通常,此类广告在 iframe 中投放。 现在,如果发布商希望吸引用户点击此类广告,他们就可以将广告设置为 通过应用 CSS 规则 iframe { opacity: 0; } 并叠加 iframe,使其完全透明 以及用户可能会想要点击的可爱猫咪视频。 这称为点击劫持。 您可以在本示例的上半部分看到此类点击劫持攻击的实际情况。 演示(尝试“观看”猫咪视频) 并激活“特技模式”)。 您会发现 iframe 中的广告“思考”获得了合法点击, 在您(伪装成无意)点击它时,展示完全透明。

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

Intersection Observer v2 如何解决此问题?

Intersection Observer v2 引入了跟踪实际“可见性”的概念,目标 因为人会定义它。 在 IntersectionObserver 构造函数, 相交 IntersectionObserverEntry 实例将包含一个名为 isVisible 的新布尔字段。 isVisibletrue 值是底层实现强有力的保证 目标元素完全未被其他内容遮挡 并且未应用会改变其在屏幕上的显示效果的视觉效果。 相比之下,false 值表示实现无法保证这一点。

关于 Ad Manager 的 规范 实现可以报告假负例(也就是说,将 isVisible 设置为 false)。 出于性能或其他方面的原因,浏览器只能使用边界 方框和直线几何图形;它们不会试图针对 例如 border-radius

也就是说,在任何情况下都不允许出现假正例(也就是说, isVisible 更改为 true(当目标元素并非完全可见且未经修改时)。

新代码的实际应用是什么样的?

IntersectionObserver 构造函数现在接受另外两个配置属性:delaytrackVisibilitydelay 是一个数字,表示两次通知之间的最短延迟时间(以毫秒为单位)。 指定目标的观察器。 trackVisibility 是一个布尔值,指示观察器是否会跟踪目标 可见性。

请务必注意,当 trackVisibilitytrue 时,delay 必须位于 至少 100(即每 100 毫秒不超过一条通知)。 如前所述,计算可见性的成本很高,而此要求旨在防止 性能下降和电池消耗相关开发者将使用 最大可容许值

根据当前的 spec,可见性是 计算方法如下:

  • 如果观察者的 trackVisibility 属性为 false,则目标会被视为可见。 这与当前的 v1 行为相对应。

  • 如果目标具有除 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 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 网站中的主打图片。