IntersectionObservers 可让您知道被观察的元素何时进入或退出浏览器的视口。
假设您想跟踪 DOM 中的某个元素何时进入可见视口。您可能需要这样做,以便及时延迟加载图片,或者因为您需要知道用户是否确实在查看某个广告横幅。为此,您可以挂接滚动事件,也可以使用定期计时器并对该元素调用 getBoundingClientRect()。
不过,这种方法非常慢,因为每次调用 getBoundingClientRect() 都会强制浏览器重新布局整个网页,并会给您的网站带来相当大的卡顿。如果您知道自己的网站是在 iframe 中加载的,并且想知道用户何时可以看到某个元素,那么事情几乎是不可能的。单源模型和浏览器不允许您访问包含 iframe 的网页中的任何数据。这是广告的常见问题,例如,广告经常使用 iframe 加载。
IntersectionObserver 旨在提高此可见性测试的效率,并且已在所有现代浏览器中实现。IntersectionObserver 会在观察到的元素进入或退出浏览器的视口时通知您。
如何创建IntersectionObserver
该 API 相当小,最好通过示例进行描述:
const io = new IntersectionObserver(entries => {
console.log(entries);
}, {
/* Using default options. Details below */
});
// Start observing an element
io.observe(element);
// Stop observing an element
// io.unobserve(element);
// Disable entire IntersectionObserver
// io.disconnect();
使用 IntersectionObserver 的默认选项时,当元素部分进入视图以及完全离开视口时,系统都会调用您的回调。
如果您需要观察多个元素,则可以并且建议使用同一 IntersectionObserver 实例,通过多次调用 observe() 来观察多个元素。
系统会将 entries 参数传递给您的回调,该参数是一个 IntersectionObserverEntry 对象数组。每个此类对象都包含一个被观察元素的更新后的交集数据。
🔽[IntersectionObserverEntry]
time: 3893.92
🔽rootBounds: ClientRect
bottom: 920
height: 1024
left: 0
right: 1024
top: 0
width: 920
🔽boundingClientRect: ClientRect
// ...
🔽intersectionRect: ClientRect
// ...
intersectionRatio: 0.54
🔽target: div#observee
// ...
rootBounds 是对根元素(默认情况下为视口)调用 getBoundingClientRect() 的结果。boundingClientRect 是对观察到的元素调用 getBoundingClientRect() 的结果。intersectionRect 是这两个矩形的交集,可有效告知您所观察元素的哪个部分可见。intersectionRatio 与此密切相关,它会告诉您元素的可见比例。有了这些信息,您现在可以实现一些功能,例如在素材资源显示在屏幕上之前及时加载。高效。
IntersectionObserver 会异步传送其数据,并且您的回调代码将在主线程中运行。此外,该规范实际上指出 IntersectionObserver 实现应使用 requestIdleCallback()。这意味着,对您提供的回调的调用具有较低的优先级,并且将由浏览器在空闲时间进行。这是有意为之的设计决策。
滚动 div
我不太喜欢在元素内滚动,但我不会对此做出评判,IntersectionObserver 也不会。options 对象接受 root 选项,该选项可让您定义视口的替代项作为根。请务必注意,root 必须是所有被观察元素的祖先。
交集所有内容!
不需要!Bad developer! 这并不是对用户 CPU 周期的合理利用。我们以无限滚动条为例:在这种情况下,绝对建议向 DOM 添加哨兵并观察(和回收!)这些哨兵。您应在无限滚动条中的最后一项附近添加一个哨兵。当该标记进入视野时,您可以使用回调来加载数据、创建后续内容、将其附加到 DOM,并相应地重新定位标记。如果您正确回收了哨兵,则无需额外调用 observe()。IntersectionObserver会继续运行。
请提供更多更新
如前所述,当被观察元素部分进入视图时,回调会被触发一次;当该元素离开视口时,回调会再次被触发。这样,IntersectionObserver 就可以回答“元素 X 是否在视图中?”这个问题。但在某些应用场景中,这可能还不够。
这时,threshold 选项就派上用场了。它可让您定义一个 intersectionRatio 阈值数组。每当 intersectionRatio 跨越其中一个值时,系统都会调用您的回调。threshold 的默认值为 [0],这解释了默认行为。如果我们将 threshold 更改为 [0, 0.25, 0.5, 0.75, 1],则每当元素有额外四分之一的部分变得可见时,我们都会收到通知:
还有其他选项吗?
目前,除了上面列出的选项之外,只有一个额外选项。rootMargin 允许您指定根元素的边距,从而有效地扩大或缩小用于交集的区域。这些外边距使用 CSS 样式字符串(如 "10px 20px 30px 40px")指定,分别指定上、右、下和左外边距。总而言之,IntersectionObserver 选项结构体提供以下选项:
new IntersectionObserver(entries => {/* … */}, {
// The root to use for intersection.
// If not provided, use the top-level document's viewport.
root: null,
// Same as margin, can be 1, 2, 3 or 4 components, possibly negative lengths.
// If an explicit root element is specified, components may be percentages of the
// root element size. If no explicit root element is specified, using a
// percentage is an error.
rootMargin: "0px",
// Threshold(s) at which to trigger callback, specified as a ratio, or list of
// ratios, of (visible area / total area) of the observed element (hence all
// entries must be in the range [0, 1]). Callback will be invoked when the
// visible ratio of the observed element crosses a threshold in the list.
threshold: [0],
});
<iframe> 魔法
IntersectionObserver 专为广告服务和社交网络 widget 而设计,这些服务和 widget 经常使用 <iframe> 元素,并且可以从了解这些元素是否在视图中受益。如果 <iframe> 观察其某个元素,则滚动 <iframe> 以及滚动包含 <iframe> 的窗口都会在适当的时间触发回调。不过,对于后一种情况,rootBounds 将设置为 null,以避免跨源泄露数据。
IntersectionObserver 不涉及哪些内容?
需要注意的是,IntersectionObserver 并非有意追求像素完美或低延迟。使用它们来实现滚动相关动画等操作必然会失败,因为严格来说,在您使用这些数据时,数据已经过时。说明中详细介绍了 IntersectionObserver 的原始用例。
我可以在回调中执行多少工作?
简短:在回调中花费太多时间会导致应用滞后,所有常见做法都适用。
继续操作,让元素相交
IntersectionObserver 的浏览器支持情况良好,所有现代浏览器都支持该功能。如有必要,可在旧版浏览器中使用 polyfill,该 polyfill 可在 WICG 的代码库中找到。显然,使用该 polyfill 无法获得原生实现所带来的性能优势。
您可以立即开始使用 IntersectionObserver!告诉我们您想到了什么。