Intersection Observer 的一些实用功能:了解元素何时进入视野

Avatar of Preethi
Preethi

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

您可能不知道,JavaScript 最近悄然积累了相当数量的观察器,而 Intersection Observer 就是其中之一。观察器是实时发现某些事物的对象——就像观鸟者去他们喜欢的地方坐下,等待鸟儿到来一样。

不同的观察器观察不同的东西(并非每个人都观察鹰)。

我最早了解的观察器是 Mutation Observer,它用于查找 DOM 树的更改。它当时是独一无二的,但现在我们有了更多观察器。

Intersection Observer 观察元素与其祖先元素之一或页面可见区域(视口)之间的“交叉”(即穿过)。

这有点像观察火车穿过车站。您可以看到火车何时进站、何时离站以及它停留在站台的时间。

了解元素即将进入视野、是否已移出视野或自进入视野以来已过去多长时间,所有这些都有实用的应用。因此,我们现在将了解一些这些用例——在通过 Intersection Observer API 创建 IntersectionObserver 对象的代码之后。

IntersectionObserver 的快速概述

在撰写本文时,Intersection Observer API 已经获得了广泛的支持。

此浏览器支持数据来自 Caniuse,其中包含更多详细信息。数字表示浏览器从该版本开始支持该功能。

桌面

ChromeFirefoxIEEdgeSafari
5855不支持1612.1

移动/平板电脑

Android ChromeAndroid FirefoxAndroidiOS Safari
12712712712.2-12.5

但是,如果您想在使用 Intersection Observer 时检查它是否受支持,您可以查看 window 对象中是否存在 IntersectionObserver 属性。

if(!!window.IntersectionObserver){}
/* or */
if('IntersectionObserver' in window){}

好的,现在来看一下对象的创建。

var observer = new IntersectionObserver(callback, options);

IntersectionObserver 对象的构造函数接受两个参数。第一个参数是回调函数,当观察器注意到交叉并异步传递了一些关于该交叉的数据时,该函数将被执行。

第二个(可选)参数是选项,一个包含信息的 对象,用于定义什么将是“交叉”。我们可能不想知道元素何时即将进入视野,而只想在它完全可见时知道。诸如此类的情况是通过选项参数定义的。

选项有三个属性

  • root – 被观察元素将与其交叉的祖先元素/视口。可以将其视为火车将与其交叉的车站。
  • rootMargin – 根元素的周长,缩小或扩大根元素的区域以注意交叉。它类似于 CSS margin 属性。
  • threshold – 值(介于 0 和 1.0 之间)的数组,每个值表示元素与根交叉或穿过根的距离,回调将在该距离触发。

假设我们的阈值为 0.5。当元素处于或超过其半可见阈值时,将触发回调。如果值为 [0.3, 0.6],则当元素处于或超过其 30% 可见阈值以及其 60% 可见阈值时,将触发回调。

现在理论讲得够多了。让我们看一些演示。首先是延迟加载。

用例 1:延迟加载图像

查看 CodePen 上 Preethi Sam (@rpsthecoder) 编写的笔
Intersection Observer – 延迟加载

CodePen 上。

要查看加载标记,请查看此 网页,因为嵌入式演示不会显示该内容。

CSS-Tricks 之前已经全面介绍了延迟加载,通常是这样完成的:在预期放置图像的位置显示一个轻量级占位符,然后在它们进入(或即将进入)视野时用预期图像替换它们。相信我,实现这一点一点也不懒惰——也就是说,直到我们获得 可以使用的原生内容

我们将应用相同的机制。首先,我们有一堆图像,并定义了一个最初显示的占位符图像。我们使用一个数据属性来承载要显示的原始图像的 URL,该 URL 定义了在图像进入视野时要加载的实际图像。

<img src="placeholder.png" data-src="img-1.jpg">
<img src="placeholder.png" data-src="img-2.jpg">
<img src="placeholder.png" data-src="img-3.jpg">
<!-- more images -->

其余的是脚本。

let observer = new IntersectionObserver(
(entries, observer) => { 
  entries.forEach(entry => {
    /* Here's where we deal with every intersection */
  });
}, 
{rootMargin: "0px 0px -200px 0px"});

上面的回调函数是一个箭头函数(尽管您可以使用普通函数代替)。

回调函数接收两个参数:一组包含有关每个交叉的信息的条目观察器本身。可以过滤或循环遍历这些条目,以便我们可以处理所需的交叉条目。至于选项,我只提供了 rootMargin 值,让 rootthreshold 属性采用其默认值。

root 的默认值为视口,threshold 的默认值为 0——这大致可以翻译为“元素出现在视口的那一刻就通知我!”

不过,奇怪的是,我使用 rootMargin 将视口的观察区域底部缩小了 200 像素。我们通常不会为延迟加载执行此操作。相反,我们可能会增加边距或让它默认为空。但是,在这种情况下,我们通常不会进行缩小操作。我这样做只是因为我想演示观察区域中阈值处加载的原始图像。否则,所有操作都将发生在视野之外。

当图像与视口的观察区域(在演示中为底部上方 200 像素)交叉时,我们将占位符图像替换为实际图像。

let observer = new IntersectionObserver(
(entries, observer) => { 
entries.forEach(entry => {
    /* Placeholder replacement */
    entry.target.src = entry.target.dataset.src;
    observer.unobserve(entry.target);
  });
}, 
{rootMargin: "0px 0px -200px 0px"});

entry.target 是观察器观察的元素。在我们的例子中,这些是图像元素。一旦图像元素中的占位符被替换,我们就无需再观察它了,因此我们为此调用观察器的 unobserve 方法。

现在观察器已准备就绪,是时候使用其 observer 方法开始观察所有图像了。

document.querySelectorAll('img').forEach(img => { observer.observe(img) });

就是这样!我们已经延迟加载了图像。继续下一个演示。

用例 2:当视频移出视野时自动暂停

假设我们在 YouTube 上观看视频,并且(无论出于何种原因)我们想向下滚动以阅读评论。我不知道您是否和我一样,但我通常不会先暂停视频再执行此操作,这意味着我在浏览时错过了部分视频。

如果我们在滚动离开视频时,视频会自动暂停,那不是很好吗?如果视频在重新进入视野时恢复播放,那就更好了,这样就无需点击播放或暂停按钮了。

Intersection Observer 当然可以做到这一点。

查看 CodePen 上 Preethi Sam (@rpsthecoder) 编写的笔
IntersectionObserver:自动暂停视野外的视频

CodePen 上。

这是我们在 HTML 中的视频

<video src="OSRO-animation.mp4" controls=""></video>

以下是我们在播放和暂停之间切换的方式

let video = document.querySelector('video');
let isPaused = false; /* Flag for auto-paused video */
let observer = new IntersectionObserver((entries, observer) => { 
  entries.forEach(entry => {
    if(entry.intersectionRatio!=1  && !video.paused){
      video.pause(); isPaused = true;
    }
    else if(isPaused) {video.play(); isPaused=false}
  });
}, {threshold: 1});
observer.observe(video);

在向您展示如何在每次交叉(即条目)期间暂停和播放视频之前,我想提请您注意选项的 threshold 属性。

Th threshold 的值为 1。rootrootMargin 将默认为空。这等同于说:“当元素完全出现在视口中时,请通知我。”

一旦发生交叉并触发回调,我们将根据以下逻辑暂停或播放视频

A flow chart for toggling play and pause on a video, where if video not fully visible and not paused, then isPaused is true. But if video was auto-paused, then IsPaused is false.

我没有为视频调用 unobserve,因此观察器会持续观察视频,并在它每次移出视野时暂停。

用例 3:查看查看了多少内容

这可以根据您的内容和您衡量内容查看量的偏好方式以多种方式解释和实现。

对于一个简单的示例,我们将观察页面上文章列表中每篇文章的最后一段。一旦文章的最后一段完全可见,我们将认为该文章已阅读——就像我们可能说看到火车的最后一节车厢就等于看到了整列火车一样。

这是一个演示,显示了页面上的两篇文章,每篇文章包含多段。

查看 CodePen 上 Preethi Sam (@rpsthecoder) 编写的笔
IntersectionObsever:查看的内容

CodePen 上。

我们简化的 HTML 类似于以下内容

<div id="count"><!-- The place where "number of articles viewed" is displayed --></div>

<h2>Article 1</h2>
<article>
  <p><!-- Content --></p>
  <!-- More paragraphs -->
</article>
<h2>Article 2</h2>
<article>
  <p><!-- Content --></p>
  <!-- More paragraphs -->
</article>
<!-- And so on... -->
let n=0; /* Total number of articles viewed */
let count = document.querySelector('#count');
let observer = new IntersectionObserver((entries, observer) => { 
  entries.forEach(entry => {
    if(entry.isIntersecting){
      count.textContent= `articles fully viewed - ${++n}`; 
      observer.unobserve(entry.target);
    }
  });
}, {threshold: 1});

document.querySelectorAll('article > p:last-child').forEach(p => { observer.observe(p) });

在每次交叉点(文章最后一段的完整视图)出现时,我们都会递增一个计数:n,它表示已阅读文章的总数。然后,我们在文章列表上方显示该数字。

一旦我们在最后一段的交叉点进行了计数,就不需要再观察它了,因此我们对其调用unobserve

感谢您的关注!

对于本文我们将一起查看的示例,就到此为止了。您可能已经了解了如何使用它,以便能够观察元素并根据它们与视口交叉的位置触发事件。

也就是说,在根据通过观察者获得的交叉数据进行视觉更改时,需要谨慎。当然,在记录交叉数据方面,Intersection Observer 非常方便。但当它用于进行屏幕上的更改时,我们需要确保更改不会出现延迟,这是一种可能性,因为我们基本上是根据异步检索的数据进行更改。这可能需要一些时间来加载。

正如我们所看到的,每个交叉条目都具有一组属性,用于传达有关交叉的信息。在本篇文章中,我没有介绍所有属性,因此请务必查看它们