IntersectionObserver
假设你想追踪 DOM 里的一个元素是否进入 viewport 的可视区。你做这件事可能是因为你想在这个时机 lazyload 图片或者你需要知道用户是否真的在看一个某个广告横幅。你可以通过添加 scroll
事件钩子或使用定时器去调用元素的 getBoundingClientRect()
方法。然而,这种方法真的很慢,因为每次调用 getBoundingClientRect()
会造成浏览器 re-layout 当前页面 并会在你的页面造成很大的卡顿和闪烁。 iframe
里的元素是否可见这样的事情几乎很难做到。 基于单一源模型,浏览器不会让你访问包含 iframe
的页面的任何数据。这是频繁使用 iframe
加载广告的常见问题。
为了让可见度测试更有效率, IntersectionObserver 被设计出来了。IntersectionObserver
让你知道观察的元素何时进入或退出浏览器 viewport
。
如何创建一个 IntersectionObserver
API 相当简单,例子一看就懂:
var io = new IntersectionObserver(
entries => {
console.log(entries);
},
{
/* 使用默认选项,详细在下面会介绍 */
}
);
// 开始观察一个元素
io.observe(element);
// 停止观察一个元素
// io.unobserve(element);
// 禁用整个 IntersectionObserver
// io.disconnect();
使用默认选项,回调会在元素进入和完全退出 viewport
的时候触发。
如果你需要观察多个元素,建议在同一个 IntersectionObserver
实例上多次调用 observe()
来观察多个元素,这也是允许的调用方式。
回调函数回传的参数是一个数组对象 IntersectionObserverEntry
。每个这样的对象都包含了每个观察元素的最新数据。
🔽[IntersectionObserverEntry]
🔽 0: 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
是对 root 元素调用getBoundingClientRect
的结果,默认 root 元素是 viewport 。boundingClientRect
是被观察元素调用getBoundingClientRect
的结果。intersectionRect
是两个矩形的交叉部分,可能有效地告诉你被观察的元素的哪一部分是可见的。intersectionRatio
是最相关的,告诉你元素有百分之几是可见的。
有了这些信息,你现在可以实现元素进入 viewport 前及时加载的特性。非常有用。
IntersectionObservers
是异步推送他们的数据,而回调是运行在主线程的。另外,规范提到 IntersectionObserver
应该使用 requestIdleCallback()
。 这意味着调用回调函数的优先级是非常低的,只会在浏览器有空闲的时候调用。这是有意设计的。
Scrolling divs
我并不喜欢在 div 中滚动元素, 但我不会用 scroll
判定, 而是用 IntersectionObserver
。 option
对象有一个 root
选项让你定义一个代替 viewport 的 root 元素。需要记住的是,要保证 root 元素是所有被观察元素的祖先。
重叠所有元素
不!那是糟糕的开发者!请注意使用用户的 CPU 周期。让我们考虑一下无限滚动的例子: 在脚本中,添加一个哨兵来观察和回收是明智的选择。你应该在无限滚动的最后一个元素添加哨兵。当哨兵快要进入 viewport 的时候,在回调函数中加载数据,创建接下来的元素,并添加到哨兵之前的 DOM 结构中。如果重复利用哨兵,不需要再调用 observe()
, IntersectionObserver
仍然会继续工作。
更多地更新与回调调用
就像前面提到的,当被监听元素进入或离开 viewport 的时候,回调函数会分别单次触发。 这让IntersectionObserver
可以给你这个问题的答案,“元素 X 是否在视图中?”。但在某些场景中,这还不够。
theshold
选项登场。它允许你定义一个 intersectionRatio
阈值数组。当达到每一个 intersectionRatio
值时,回调函数都会被调用。theshold
默认值是 [0]
,就是我们解释的默认表现。当设定 theshold
为 [0, 0.25, 0.5, 0.75, 1]
, 我们会在元素每四分之一的部分进入 viewport 时得到通知。
还有其他问题吗?
到现在为止,还有一个选项没有被提到。rootMargin
允许你指定 root
元素的 margin
,有效地允许你增加或缩减实际的交叉区域。margin
使用 css-style 的规则, 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
被设计时明确考虑广告服务和网络社交服务,可能经常会用到 iframe
和有益于知道他们是否在看。如果观察 iframe
中的元素,那么滚动 iframe
和滚动父页面都可以在适当的时候触发回调函数。对于后者, rootBounds
将被设置为 null
以避免数据源泄漏。
IntersectionObserver
不是什么?
需要知道的是, IntersectionObserver
是被故意设置成不完美和低延迟的。使用它们努力实现像滚动动画之类是注定会失败的。因为严格地说,当你获得数据时,数据已经过时了。这里有更多的关于 IntersectionObserver
原始用例。
可以在 callback
中做多少事情
在回调中花太多时候会让你的应用滞后,和常见的实践一样。
使用 IntersectionObserver
对于 IntersectionObserver
,浏览器的支持仍然很弱,所以它不会马上被普遍应用。同时,WICG 的 polyfill 也被建立起来。显然,使用 polyfill 不如原生实现的效率好。
Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code samples are licensed under the Apache 2.0 License. For details, see our Site Policies. Java is a registered trademark of Oracle and/or its affiliates. 上次更新日期:六月 5, 2017
【翻译原文】: intersectionobserver