Intersection Observer API
Baseline 广泛可用 *
Intersection Observer API 提供了一种异步观察目标元素与祖先元素或顶级文档的视口的交集变化的方法。
概述
过去,检测元素的可见性,或两个元素之间相对可见性一直是一项困难的任务,其解决方案不可靠,并且容易导致浏览器和用户访问的网站运行缓慢。随着网络的成熟,对这种信息的需求不断增长。需要交叉信息的原因有很多,例如:
- 在页面滚动时对图像或其他内容进行懒加载。
- 实现“无限滚动”网站,随着您的滚动,会加载并渲染越来越多的内容,这样用户就不必翻页。
- 报告广告可见性以计算广告收入。
- 根据用户是否会看到结果来决定是否执行任务或动画过程。
过去实现交叉检测涉及到事件处理程序和循环调用诸如 Element.getBoundingClientRect() 之类的方法,以构建每个受影响元素所需的信息。由于所有这些代码都在主线程上运行,即使其中一个也可能导致性能问题。当网站加载了这些测试时,情况可能会变得非常糟糕。
考虑一个使用无限滚动的网页。它使用供应商提供的库来管理页面中定期放置的广告,页面中到处都有动画图形,并使用自定义库来绘制通知框等。这些都拥有自己的交叉检测例程,所有这些都在主线程上运行。网站作者甚至可能没有意识到这种情况正在发生,因为他们可能对所使用的两个库的内部工作知之甚少。当用户滚动页面时,这些交叉检测例程在滚动处理代码期间不断触发,导致用户对浏览器、网站和他们的计算机感到沮丧。
Intersection Observer API 允许代码注册一个回调函数,该函数在特定元素进入或离开与另一个元素(或视口)的交集时,或当两个元素之间的交集以指定量发生变化时执行。通过这种方式,网站不再需要在主线程上执行任何操作来监视这种元素交集,并且浏览器可以自由地根据需要优化交集的管理。
Intersection Observer API 无法做的一件事:根据重叠像素的确切数量或具体是哪些像素触发逻辑。它只解决了常见的用例:“如果它们大约重叠 N%,我需要做些什么。”
概念与用法
Intersection Observer API 允许您配置一个回调,当发生以下任一情况时调用该回调:
- 一个目标元素与设备的视口或指定的元素相交。该指定的元素在 Intersection Observer API 中称为根元素或根。
- 观察器首次被要求观察目标元素。
通常,您会希望观察目标元素与其最近的可滚动祖先的交集变化,或者,如果目标元素不是可滚动元素的后代,则观察设备的视口。要观察与设备视口相关的交集,请将 root 选项指定为 null。继续阅读以获取有关交集观察器选项的更详细解释。
无论您使用视口还是其他元素作为根,API 的工作方式都是相同的,它会在目标元素的可见性发生变化以使其与根的交集达到所需量时执行您提供的回调函数。
目标元素与其根之间的交集程度是交集比率。这是一个表示目标元素可见百分比的值,介于 0.0 和 1.0 之间。
创建交集观察器
通过调用其构造函数并向其传递一个回调函数来创建交集观察器,该函数将在阈值向任一方向越过时运行:
const options = {
root: document.querySelector("#scrollArea"),
rootMargin: "0px",
scrollMargin: "0px",
threshold: 1.0,
};
const observer = new IntersectionObserver(callback, options);
阈值为 1.0 意味着当 100% 的目标在 root 选项指定的元素中可见时,将调用回调。
交集观察器选项
传递给 IntersectionObserver() 构造函数的 options 对象允许您控制调用观察器回调的条件。它具有以下字段:
根-
用作检查目标可见性的视口的元素。必须是目标的祖先。如果未指定或为
null,则默认为浏览器视口。 rootMargin-
根周围的边距。一个由一到四个值组成的字符串,类似于 CSS
margin属性,例如"10px 20px 30px 40px"(上、右、下、左)。值只能是像素 (px) 或百分比 (%)。这组值用于在计算交集之前增大或缩小根元素的边界框的每一侧。负值会缩小根元素的边界框,正值会扩大它。如果未指定,默认值为"0px 0px 0px 0px"。 scrollMargin-
嵌套滚动容器周围的边距,其值/默认值与
rootMargin相同。在计算交集之前,边距应用于嵌套的可滚动容器。正值会增大容器的裁剪矩形,允许目标在变得可见之前相交,而负值会缩小裁剪矩形。 threshold-
单个数字或数字数组,指示在目标可见性的哪个百分比时应执行观察器的回调。如果您只想检测可见性何时超过 50% 标记,可以使用值 0.5。如果您希望在可见性每次超过另一个 25% 时运行回调,您可以指定数组 [0, 0.25, 0.5, 0.75, 1]。默认值为 0(表示即使有一个像素可见,也将运行回调)。值 1.0 意味着直到每个像素都可见才认为阈值已通过。
delay实验性-
当跟踪目标可见性(trackVisibility 为
true)时,这可用于设置此观察器通知之间的最小延迟(以毫秒为单位)。限制通知速率是可取的,因为可见性计算是计算密集型的。如果跟踪可见性,对于任何小于 100 的值,该值将设置为 100,并且您应该使用最大可容忍值。该值默认为 0。 trackVisibility实验性-
一个布尔值,指示此
IntersectionObserver是否正在跟踪目标可见性的变化。当为
false时,当目标元素滚动到根元素的视口时,浏览器将报告交集。当为true时,浏览器将额外检查目标是否实际可见,并且没有被其他元素覆盖,或者可能被滤镜、降低的不透明度或某些变换扭曲或隐藏。默认值为false,因为跟踪可见性是计算密集型的。如果设置此项,也应设置一个delay。
交集变化回调
传递给 IntersectionObserver() 构造函数的回调接收 IntersectionObserverEntry 对象的列表和观察器:
const callback = (entries, observer) => {
entries.forEach((entry) => {
// Each entry describes an intersection change for one observed
// target element:
// entry.boundingClientRect
// entry.intersectionRatio
// entry.intersectionRect
// entry.isIntersecting
// entry.rootBounds
// entry.target
// entry.time
});
};
回调接收到的条目列表包含每个阈值交叉事件的一个 IntersectionObserverEntry 对象——可以同时接收多个条目,无论是来自多个目标还是来自单个目标在短时间内交叉多个阈值。条目使用队列分派,因此它们应该按生成时间排序,但您最好使用 IntersectionObserverEntry.time 正确排序它们。每个条目都描述了给定元素与根元素的交集程度,元素是否被认为正在相交,等等。该条目只包含有关该特定瞬间的信息——如果您想要需要随时间跟踪的信息,例如滚动方向和速度,您可能需要通过记住以前收到的条目自行计算。
请注意,您的回调在主线程上执行。它应该尽可能快地运行;如果需要做任何耗时的事情,请使用 Window.requestIdleCallback()。
下面的代码片段显示了一个回调,该回调记录了元素从不与根相交到至少 75% 相交的次数。对于阈值 0.0(默认值),回调大约在 isIntersecting 的布尔值转换时调用。因此,该片段首先检查转换是否为正,然后确定 intersectionRatio 是否高于 75%,在这种情况下它会增加计数器。
const intersectionCallback = (entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
let elem = entry.target;
if (entry.intersectionRatio >= 0.75) {
intersectionCounter++;
}
}
});
};
指定要观察的元素
创建观察器后,您需要为其指定一个要观察的目标元素:
const target = document.querySelector("#listItem");
observer.observe(target);
// the callback we set up for the observer will be executed now for the first time
// it waits until we assign a target to our observer (even if the target is currently not visible)
每当目标达到为 IntersectionObserver 指定的阈值时,都会调用回调。
此外,请注意,如果您指定了 root 选项,则目标必须是根元素的后代。
如何计算交集
Intersection Observer API 考虑的所有区域都是矩形;不规则形状的元素被认为占据了包含元素所有部分的最 小矩形。同样,如果元素的可见部分不是矩形,则元素的交集矩形被认为是包含元素所有可见部分的最 小矩形。
了解 IntersectionObserverEntry 提供的各种属性如何描述交集非常有用。
交集根和根边距
在我们可以跟踪元素与容器的交集之前,我们需要知道容器是什么。该容器是交集根或根元素。这可以是文档中观察元素的祖先的特定元素,也可以是 null 以使用文档的视口作为容器。
根交集矩形是用于与目标进行检查的矩形。该矩形是这样确定的:
- 如果交集根是隐式根(即顶级
Document),则根交集矩形是视口的矩形。 - 如果交集根具有溢出剪裁,则根交集矩形是根元素的内容区域。
- 否则,根交集矩形是交集根的边界客户端矩形(通过调用其上的
getBoundingClientRect()返回)。
通过在创建 IntersectionObserver 时设置根边距 rootMargin,可以进一步调整根交集矩形。rootMargin 中的值定义了添加到交集根边界框每一侧的偏移量,以创建最终的交集根边界(在执行回调时在 IntersectionObserverEntry.rootBounds 中公开)。正值会增大框,而负值会缩小框。每个偏移值只能以像素 (px) 或百分比 (%) 表示。
使用根边距增大框的效果是允许溢出目标在变得可见之前与根相交。这可以用于,例如,在图像进入视图之前就开始加载它们,而不是在它们变得可见时才加载。
在下面的示例中,我们有一个可滚动框和一个最初不可见的元素。您可以调整根右边距,并看到:
- 如果边距为正,即使红色元素不可见,它也被认为与根相交,因为它与根的边距区域相交。
- 如果边距为负,那么即使红色元素开始变得可见,它仍然不被认为与根相交,因为根的边界框已缩小。
交集根和滚动边距
考虑您有一个根元素包含嵌套的滚动容器的情况,并且您想要观察与其中一个可滚动容器内的目标的交集。默认情况下,当目标在其根定义的区域内可见时,与目标元素的交集开始变得可观察;换句话说,当容器滚动到根中并目标滚动到其容器的裁剪矩形中时。
您可以使用滚动边距在目标滚动到其滚动容器内视图之前或之后开始观察交集。边距被添加到根中的所有嵌套滚动容器,包括根元素(如果它也是滚动容器),并且具有增大(正边距)或缩小(负边距)用于计算交集的裁剪区域的效果。
注意:您可以在每个您想要滚动边距的滚动容器上创建一个交集观察器,并使用根边距属性实现类似的效果。使用滚动边距更符合人体工程学,因为在大多数情况下,您只需为所有嵌套目标设置一个交集观察器。
在下面的示例中,我们有一个可滚动框和一个最初不可见的图像轮播。根元素上的观察器观察轮播内的图像元素目标。当图像元素开始与根元素相交时,图像被加载,交集被记录,并且观察器被移除。
向下滚动以显示轮播。可见图像应立即加载。如果您滚动轮播,您应该观察到图像在元素变得可见时立即加载。
重置示例后,您可以使用提供的控件更改滚动边距百分比。如果您设置一个正值(例如 20%),则滚动容器的剪切矩形将增加 20%,您应该观察到图像在进入视图之前就被检测并加载。同样,负值将意味着一旦图像已在视图中,就会检测到交集。
阈值
Intersection Observer API 不会报告目标元素可见性的每个微小变化,而是使用阈值。创建观察器时,您可以提供一个或多个数字值,表示目标元素的可见百分比。然后,API 只报告跨越这些阈值的可见性变化。
例如,如果您想在目标可见性每次向前或向后通过每个 25% 标记时收到通知,您将在创建观察器时将数组 [0, 0.25, 0.5, 0.75, 1] 指定为阈值列表。
调用回调时,它会收到一个 IntersectionObserverEntry 对象列表,每个观察到的目标一个,其与根的交集程度发生变化,使得暴露量跨越其中一个阈值,无论方向如何。
您可以通过查看条目的 isIntersecting 属性来查看目标当前是否与根相交;如果其值为 true,则目标至少部分与根元素或文档相交。这使您可以确定该条目表示元素从相交到不再相交的转换,还是从不相交到相交的转换。
请注意,可能存在零交集矩形,这可能发生在交集恰好沿两者之间的边界或 boundingClientRect 面积为零的情况下。目标和根共享边界线的这种状态不足以被认为是过渡到相交状态。
为了了解阈值的工作原理,请尝试滚动下面的框。其中每个彩色框都显示了自身在所有四个角中可见的百分比,因此您可以在滚动容器时看到这些比率随时间变化。每个框都有一组不同的阈值:
- 第一个框的每个可见百分点都有一个阈值;也就是说,
IntersectionObserver.thresholds数组是[0.00, 0.01, 0.02, /*…,*/ 0.99, 1.00]。 - 第二个框只有一个阈值,在 50% 标记处。
- 第三个框每 10% 的可见度(0%、10%、20% 等)都有阈值。
- 最后一个框每 25% 有阈值。
跟踪可见性和延迟
默认情况下,当目标元素滚动到根元素的视口时,观察器会提供通知。虽然在许多情况下只需要这样做,但有时在目标“视觉上受损”时,不报告交集很重要。例如,在测量分析或广告展示时,目标元素没有被隐藏或扭曲(无论是全部还是部分)很重要。
trackVisibility 设置会告诉观察器仅报告浏览器不认为视觉上受损的目标的交集,例如通过改变不透明度或应用滤镜或变换。该算法是保守的,可能会省略技术上可见的元素,例如那些只有轻微不透明度降低的元素。
可见性计算是计算密集型的,只有在必要时才应使用。当跟踪可见性时,还应设置 delay 以限制最小报告周期。建议您将延迟设置为最大可容忍值(跟踪可见性时的最小延迟为 100 毫秒)。
剪裁和交集矩形
浏览器按如下方式计算最终的交集矩形;所有这些都是为您完成的,但了解这些步骤有助于更好地理解何时会发生交集。
- 通过调用目标上的
getBoundingClientRect()获取目标元素的边界矩形(即完全包围构成元素的所有组件的边界框的最小矩形)。这是交集矩形可能的最大值。其余步骤将移除任何不相交的部分。 - 从目标的直接父块开始并向外移动,将每个包含块的剪裁(如果有)应用于交集矩形。块的剪裁是根据两个块的交集和
overflow属性指定的剪裁模式(如果有)确定的。将overflow设置为除visible以外的任何值都会导致发生剪裁。 - 如果其中一个包含元素是嵌套浏览上下文的根(例如包含在
<iframe>中的文档),则交集矩形被剪裁到包含上下文的视口,并且向上通过容器的递归继续使用容器的包含块。因此,如果达到<iframe>的顶层,则交集矩形被剪裁到框架的视口,然后框架的父元素是下一个向交集根递归的块。 - 当向上递归到达交集根时,将结果矩形映射到交集根的坐标空间。
- 然后通过将其与根交集矩形相交来更新结果矩形。
- 最后,将此矩形映射到目标的
document的坐标空间。
接口
IntersectionObserver-
Intersection Observer API 的主要接口。提供用于创建和管理观察器的方法,该观察器可以观察任意数量的目标元素以获取相同的交集配置。每个观察器都可以异步观察一个或多个目标元素与其共享祖先元素或其顶级
Document的 视口 之间的交集变化。祖先或视口被称为根。 IntersectionObserverEntry-
描述目标元素及其根容器在特定过渡时刻的交集。此类型的对象只能通过两种方式获得:作为
IntersectionObserver回调的输入,或通过调用IntersectionObserver.takeRecords()。
一个简单的例子
这个简单的例子使目标元素在变得或多或少可见时改变其颜色和透明度。在 使用 Intersection Observer API 测量元素可见性时间中,您可以找到一个更全面的示例,展示如何测量一组元素(例如广告)对用户可见的时间,并通过记录统计数据或更新元素来响应这些信息。
HTML
本示例的 HTML 非常简短,其中包含一个主要元素,它是我们将要定位的框(其创意 ID 为 "box"),以及框内的一些内容。
<div id="box">
<div class="vertical">Welcome to <strong>The Box!</strong></div>
</div>
CSS
对于本示例的目的,CSS 并不是特别重要;它布局元素并确定 background-color 和 border 属性可以参与 CSS 过渡,我们将使用这些过渡来影响元素随着其可见性变化而发生的变化。
#box {
background-color: rgb(40 40 190 / 100%);
border: 4px solid rgb(20 20 120);
transition:
background-color 1s,
border 1s;
width: 350px;
height: 350px;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.vertical {
color: white;
font: 32px "Arial";
}
.extra {
width: 350px;
height: 350px;
margin-top: 10px;
border: 4px solid rgb(20 20 120);
text-align: center;
padding: 20px;
}
JavaScript
最后,让我们看看使用 Intersection Observer API 实现功能的 JavaScript 代码。
设置
首先,我们需要准备一些变量并安装观察器。
const numSteps = 20.0;
const boxElement = document.querySelector("#box");
let prevRatio = 0.0;
let increasingColor = "rgb(40 40 190 / ratio)";
let decreasingColor = "rgb(190 40 40 / ratio)";
createObserver();
我们在此处设置的常量和变量是:
numSteps-
一个常量,指示我们希望在可见性比率为 0.0 和 1.0 之间有多少个阈值。
prevRatio-
此变量将用于记录上次跨越阈值时的可见性比率;这将使我们能够判断目标元素是变得更可见还是更不可见。
increasingColor-
一个字符串,定义当可见性比率增加时我们将应用于目标元素的颜色。此字符串中的单词“ratio”将被替换为目标的当前可见性比率,以便元素不仅改变颜色,而且在变得不那么模糊时变得越来越不透明。
decreasingColor-
同样,这是一个字符串,定义当可见性比率降低时我们将应用的颜色。
我们使用 querySelector() 获取 ID 为 "box" 的元素的引用,然后调用我们稍后将创建的 createObserver() 方法来处理构建和安装交集观察器。
创建交集观察器
页面加载完成后,会调用 createObserver() 方法来实际创建新的 IntersectionObserver 并开始观察目标元素的过程。
function createObserver() {
const options = {
root: null,
rootMargin: "0px",
threshold: buildThresholdList(),
};
const observer = new IntersectionObserver(handleIntersect, options);
observer.observe(boxElement);
}
这首先设置了一个包含观察器设置的 options 对象。我们希望观察目标元素相对于文档视口的可见性变化,因此 root 为 null。我们不需要任何边距,因此边距偏移 rootMargin 指定为“0px”。这使得观察器观察目标元素的边界与视口边界之间的交集变化,没有任何额外的(或减去的)空间。
可见性比率阈值列表 threshold 由函数 buildThresholdList() 构建。在此示例中,阈值列表是程序化构建的,因为它们数量众多且数量可调。
一旦 options 准备就绪,我们就会创建新的观察器,调用 IntersectionObserver() 构造函数,指定在交集跨越我们的一个阈值时要调用的函数 handleIntersect(),以及我们的一组选项。然后我们在返回的观察器上调用 observe(),并将所需的目标元素传递给它。
如果需要,我们可以通过为每个元素调用 observer.observe() 来监视多个元素相对于视口的可见性交集变化。
构建阈值比率数组
构建阈值列表的 buildThresholdList() 函数如下所示:
function buildThresholdList() {
const thresholds = [];
const numSteps = 20;
for (let i = 1.0; i <= numSteps; i++) {
const ratio = i / numSteps;
thresholds.push(ratio);
}
thresholds.push(0);
return thresholds;
}
它通过将值 i/numSteps 推入 thresholds 数组来构建阈值数组——每个阈值都是 0.0 到 1.0 之间的比率,其中 i 是 1 到 numSteps 之间的每个整数。它还推入 0 以包含该值。结果,给定 numSteps 的默认值 (20),是以下阈值列表:
| # | 比率 | # | 比率 |
|---|---|---|---|
| 0 | 0.05 | 11 | 0.6 |
| 1 | 0.1 | 12 | 0.65 |
| 2 | 0.15 | 13 | 0.7 |
| 3 | 0.2 | 14 | 0.75 |
| 4 | 0.25 | 15 | 0.8 |
| 5 | 0.3 | 16 | 0.85 |
| 6 | 0.35 | 17 | 0.9 |
| 7 | 0.4 | 18 | 0.95 |
| 8 | 0.45 | 19 | 1 |
| 9 | 0.5 | 20 | 0 |
| 10 | 0.55 |
当然,我们可以将阈值数组硬编码到我们的代码中,通常您最终会这样做。但是这个示例为添加配置控件以调整粒度留下了空间,例如。
处理交集变化
当浏览器检测到目标元素(在我们的例子中是 ID 为 "box" 的元素)被揭示或遮蔽,使得其可见性比率跨越我们列表中的一个阈值时,它会调用我们的处理函数 handleIntersect():
function handleIntersect(entries, observer) {
entries.forEach((entry) => {
if (entry.intersectionRatio > prevRatio) {
entry.target.style.backgroundColor = increasingColor.replace(
"ratio",
entry.intersectionRatio,
);
} else {
entry.target.style.backgroundColor = decreasingColor.replace(
"ratio",
entry.intersectionRatio,
);
}
prevRatio = entry.intersectionRatio;
});
}
对于列表 entries 中的每个 IntersectionObserverEntry,我们查看条目的 intersectionRatio 是否正在上升;如果是,我们将目标的 background-color 设置为 increasingColor 中的字符串(请记住,它是 "rgb(40 40 190 / ratio)"),并将单词“ratio”替换为条目的 intersectionRatio。结果:颜色不仅会改变,目标元素的透明度也会改变;随着交集比率的降低,背景颜色的 alpha 值也随之降低,从而导致元素变得更透明。
同样,如果 intersectionRatio 正在下降,我们使用字符串 decreasingColor,并在设置目标元素的 background-color 之前将该字符串中的单词“ratio”替换为 intersectionRatio。
最后,为了跟踪交集比率是上升还是下降,我们将当前比率保存在变量 prevRatio 中。
结果
下面是生成的内容。上下滚动此页面,并注意在您操作时框的外观如何变化。
在使用 Intersection Observer API 测量元素可见性时间中有一个更全面的示例。
规范
| 规范 |
|---|
| 交集观察器 # intersection-observer-interface |
浏览器兼容性
加载中…