IntersectionObserver 可让您知道被观察的元素何时进入或离开浏览器的视口。
假设您想跟踪 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
的默认选项时,当元素部分进入视野和完全离开视野时,系统都会调用回调。
如果您需要观察多个元素,可以通过多次调用 observe()
来使用相同的 IntersectionObserver
实例观察多个元素,我们也建议您这样做。
系统会将 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
需要是所有被观察元素的祖先。
交叉所有内容!
不执行!开发者太差劲了!这并非明智地使用用户的 CPU 周期。我们以无限滚动条为例:在这种情况下,建议您向 DOM 添加哨兵,并观察(并回收)这些哨兵。您应在无限滚动条中的最后一个项附近添加一个哨兵。当该标记出现在视野中时,您可以使用回调加载数据、创建下一个项、将其附加到 DOM 并相应地重新定位标记。如果您正确回收了哨兵,则无需额外调用 observe()
。IntersectionObserver
会继续工作。
请提供更多动态
如前所述,当被观察元素部分进入视野范围时,系统会触发一次回调;当该元素离开视口时,系统会再次触发回调。这样一来,IntersectionObserver
就可以回答“元素 X 是否在视野中?”这个问题。不过,在某些用例中,这可能还不够。
这时,threshold
选项就派上用场了。您可以使用它来定义一个 intersectionRatio
阈值数组。每当 intersectionRatio
超过其中一个值时,系统都会调用您的回调。threshold
的默认值为 [0]
,这说明了默认行为。如果我们将 threshold
更改为 [0, 0.25, 0.5, 0.75, 1]
,则每当元素的另外 1/4 变得可见时,我们都会收到通知:
还有其他选项吗?
目前,除了上述选项外,还有一个额外的选项。借助 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
!请告诉我们您想出了什么。