IntersectionObserver 现已进入用户视野

IntersectionObserver 可让您了解被观察到的元素何时进入或退出浏览器的视口。

浏览器支持

  • 51
  • 15
  • 55
  • 12.1

来源

假设您想跟踪 DOM 中的某个元素何时进入可见视口。您可能需要这样做,以便及时延迟加载图片,或者因为您需要了解用户实际上是否正在查看某个广告横幅。为此,您可以挂接滚动事件,或者使用定期计时器并对该元素调用 getBoundingClientRect()

不过,这种方法非常慢,因为每次调用 getBoundingClientRect() 都会迫使浏览器重新布局整个页面,这会给网站带来严重的卡顿。如果您知道自己的网站是在 iframe 中加载的,并且希望知道用户何时可以看到某个元素,那么事情将近乎不可能。单源模型和浏览器不允许您访问包含 iframe 的网页中的任何数据。例如,对于经常使用 iframe 加载的广告,这是一个常见问题。

IntersectionObserver旨在提高此可见性测试的效率,此测试已支持所有现代浏览器。IntersectionObserver 可让您了解被观察到的元素何时进入或退出浏览器视口。

iframe 可见性

如何创建 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],每当该元素有额外的四分之一变得可见时,我们都会收到通知:

阈值动画。

还有其他方案吗?

截至目前,除上述选项之外,再只有一个选项。rootMargin 可让您指定根的外边距,从而有效地让您扩大或缩小用于相交的区域。这些外边距使用 CSS 样式的字符串 á la "10px 20px 30px 40px" 指定,分别指定上外边距、右外边距、下外边距和左外边距。总而言之,IntersectionObserver options 结构体提供以下选项:

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> 以及滚动包含 <iframe> 的窗口都会在适当的时间触发回调。不过,对于后一种情况,rootBounds 将设置为 null,以避免泄露各个源的数据。

IntersectionObserver”的不是是关于什么的?

需要注意的是,IntersectionObserver 特意既不是像素完美效果,也不是低延迟。使用它们来实现诸如依赖于滚动的动画等各种尝试必定会失败,因为严格来说,到使用数据的时候数据已经过时了。说明文档详细介绍了 IntersectionObserver 的原始用例。

我在回调中可以完成多少工作?

简明扼要:在回调中花费太多时间会导致应用延迟 - 所有常用做法都适用。

直奔主题,体验各种元素

浏览器对 IntersectionObserver 的支持是很好的,因为所有现代浏览器都支持此功能。如有必要,您可以在旧版浏览器中使用 polyfill,WICG 的代码库中会提供相应的 polyfill。显然,使用 polyfill 无法获得原生实现所能带来的性能优势。

你可以立即开始使用 IntersectionObserver 了!请告诉我们你的想法。