IntersectionObserver API

IntersectionObserver

假设你想追踪 DOM 里的一个元素是否进入 viewport 的可视区。你做这件事可能是因为你想在这个时机 lazyload 图片或者你需要知道用户是否真的在看一个某个广告横幅。你可以通过添加 scroll 事件钩子或使用定时器去调用元素的 getBoundingClientRect() 方法。然而,这种方法真的很慢,因为每次调用 getBoundingClientRect() 会造成浏览器 re-layout 当前页面 并会在你的页面造成很大的卡顿和闪烁。 iframe 里的元素是否可见这样的事情几乎很难做到。 基于单一源模型,浏览器不会让你访问包含 iframe 的页面的任何数据。这是频繁使用 iframe 加载广告的常见问题。

为了让可见度测试更有效率, IntersectionObserver 被设计出来了。IntersectionObserver 让你知道观察的元素何时进入或退出浏览器 viewport

https://developers.google.com/web/updates/images/2016/04/intersectionobserver/iframe.gif

如何创建一个 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 前及时加载的特性。非常有用。

https://developers.google.com/web/updates/images/2016/04/intersectionobserver/intersectratio.png

IntersectionObservers 是异步推送他们的数据,而回调是运行在主线程的。另外,规范提到 IntersectionObserver 应该使用 requestIdleCallback()。 这意味着调用回调函数的优先级是非常低的,只会在浏览器有空闲的时候调用。这是有意设计的。

Scrolling divs

我并不喜欢在 div 中滚动元素, 但我不会用 scroll 判定, 而是用 IntersectionObserveroption 对象有一个 root 选项让你定义一个代替 viewport 的 root 元素。需要记住的是,要保证 root 元素是所有被观察元素的祖先。

重叠所有元素

不!那是糟糕的开发者!请注意使用用户的 CPU 周期。让我们考虑一下无限滚动的例子: 在脚本中,添加一个哨兵来观察和回收是明智的选择。你应该在无限滚动的最后一个元素添加哨兵。当哨兵快要进入 viewport 的时候,在回调函数中加载数据,创建接下来的元素,并添加到哨兵之前的 DOM 结构中。如果重复利用哨兵,不需要再调用 observe()IntersectionObserver 仍然会继续工作。

https://developers.google.com/web/updates/images/2016/04/intersectionobserver/infinitescroller.png

更多地更新与回调调用

就像前面提到的,当被监听元素进入或离开 viewport 的时候,回调函数会分别单次触发。 这让IntersectionObserver 可以给你这个问题的答案,“元素 X 是否在视图中?”。但在某些场景中,这还不够。

theshold 选项登场。它允许你定义一个 intersectionRatio 阈值数组。当达到每一个 intersectionRatio 值时,回调函数都会被调用。theshold 默认值是 [0],就是我们解释的默认表现。当设定 theshold[0, 0.25, 0.5, 0.75, 1], 我们会在元素每四分之一的部分进入 viewport 时得到通知。

https://developers.google.com/web/updates/images/2016/04/intersectionobserver/threshold.gif

还有其他问题吗?

到现在为止,还有一个选项没有被提到。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

0%