Intersection Observer v2 不仅可以观察交叉本身,还可以检测交叉元素在交叉时是否可见。
Intersection Observer v1 可能是广受欢迎的 API 之一,现在 Safari 也支持它,因此它终于可以在所有主流浏览器中普遍使用。如需快速回顾该 API,我建议观看下面嵌入的 Intersection Observer v1 上 Surma 的 Supercharged 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 可以告知您,但不会告知您该元素是否被任何其他网页内容覆盖(即当元素被遮住时),或者元素的视觉显示设置是否已因 transform
、opacity
、filter
等视觉效果而有所修改,而这些效果可能会使元素变得不可见。
对于顶级文档中的元素,可以通过 JavaScript(例如通过 DocumentOrShadowRoot.elementFromPoint()
)分析 DOM,然后深入挖掘,来确定此信息。相比之下,如果相关元素位于第三方 iframe 中,则无法获取相同的信息。
为什么实际曝光度如此重要?
很遗憾,互联网会吸引意图更为恶劣的犯罪分子。
例如,某虚假发布商在内容网站上投放按点击付费的广告,可能会诱使他们点击广告来增加发布商的广告收入(至少在短期内,直到广告联盟发现他们)。
通常情况下,此类广告会在 iframe 中投放。
现在,如果发布商希望用户点击此类广告,则可以应用 CSS 规则 iframe { opacity: 0; }
并将广告 iframe 叠加在吸引用户的内容(例如用户真正想点击的萌猫视频)上,从而使广告 iframe 完全透明。这称为点击劫持。您可以在此演示的上半部分看到此类点击劫持攻击的实际运作方式(尝试“观看”猫视频并启用“欺骗模式”)。您会发现,iframe 中的广告会“认为”自己获得了合法点击,即使您在点击它时(假装不情愿)它是完全透明的也是如此。
Intersection Observer v2 如何解决此问题?
Intersection Observer v2 引入了根据人类对目标元素的实际“可见性”进行跟踪的概念。通过在 IntersectionObserver
构造函数中设置一个选项,交叉的 IntersectionObserverEntry
实例将包含一个名为 isVisible
的新布尔值字段。为 isVisible
设置 true
值是底层实现的强大保证,可确保目标元素完全不受其他内容遮挡,并且未应用任何会改变或扭曲其屏幕显示效果的视觉效果。相反,值 false
表示实现无法做出此保证。
该规范的一个重要细节是,该实现可以报告假负例(也就是说,将 isVisible
设置为 false
,即使目标元素完全可见且未经修改)。出于性能或其他原因,浏览器仅限于使用边界框和直线几何图形;它们不会尝试针对 border-radius
等修改实现像素级精确的结果。
不过,在任何情况下都不允许出现假正例(即,在目标元素未完全可见且未经修改时将 isVisible
设为 true
)。
新代码在实际中的样子是什么样的?
IntersectionObserver
构造函数现在还接受两个额外的配置属性:delay
和 trackVisibility
。delay
是一个数字,表示观察器针对给定目标发送通知之间的最短延迟时间(以毫秒为单位)。trackVisibility
是一个布尔值,表示观察器是否会跟踪目标的公开范围变化。
请务必注意,当 trackVisibility
为 true
时,delay
必须至少为 100
(即每 100 毫秒发送不超过一条通知)。如前所述,可见度计算成本很高,因此提出此要求是为了防止性能下降和耗电量增加。负责的开发者将为延迟使用可容许的最大值。
根据当前规范,可见度计算如下:
如果观察器的
trackVisibility
属性为false
,则目标将被视为可见。这与当前的 v1 行为一致。如果目标具有除 2D 平移或比例 2D 放大以外的有效转换矩阵,则目标会被视为不可见。
如果目标或其包含的块链中的任何元素的有效不透明度不为 1.0,则系统会将目标视为不可见。
如果目标或其所在块链中的任何元素应用了任何过滤器,则目标将被视为不可见。
如果实现无法保证目标完全不受其他网页内容遮挡,则目标会被视为不可见。
这意味着当前的实现非常保守,可以保证可见性。例如,应用几乎无法察觉的灰度滤镜(如 filter: grayscale(0.01%)
)或使用 opacity: 0.99
设置几乎不可见的透明度,都会使元素不可见。
以下是一个简短的代码示例,展示了新 API 功能。您可以在演示的第二部分中查看其点击跟踪逻辑的运作方式(但现在,请尝试“观看”小狗视频)。请务必再次启用“花哨模式”,立即将自己变成一个可疑的发布商,并了解 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'));
相关链接
- 交叉观察器规范的最新编辑者草稿。
- Chrome 平台状态中的 Intersection Observer v2。
- Intersection Observer v2 Chromium 错误。
- Blink打算实现发布功能。
致谢
感谢 Simeon Vincent、Yoav Weiss 和 Mathias Bynens 审核本文,以及 Stefan Zager 审核本文并在 Chrome 中实现此功能。Sergey Semin 在 Unshot 上使用的主打图片。