Intersection observer 简介
Intersection Observer API 提供了一种异步观察目标元素与祖先元素或顶级文档的视口相交的方法。
在以前,检测一个元素的可见性或两个元素相对于彼此的相对可见性一直是一项艰巨的任务,其解决方案不可靠且易于导致浏览器和用户访问的网页变慢。 随着网络的成熟,对此类功能的需求也在增长。 出于多种原因需要元素的可见性信息,例如:
-
滚动页面时延迟加载图像或其他内容。
-
实现“无限滚动”的网页,在您滚动时会加载和呈现越来越多的内容,从而使用户不必翻阅页面。
-
统计广告可见度,以计算广告收入。
-
根据用户是否会看到结果来决定是否执行任务或动画处理。
过去实现交叉检测通常涉及到事件处理程序,并且循环调用 Element.getBoundingClientRect()之类的方法来为每个受影响的元素建立所需的信息。 由于所有这些代码都在主线程上运行,因此即使只有一个,也可能会导致性能问题。 当网站加载了这些测试时,事情可能会变得很难看。
考虑使用无限滚动的网页。它使用供应商提供的库来管理在整个页面中定期放置的广告,这些广告具有动画图形,使用自定义库绘制通知框等等。每个元素都有其自己的相交检测程序,它们均在主线程上运行。该网站的作者甚至可能没有意识到这种情况的发生,因为他们可能对所使用的两个库的内部运作了解得很少。当用户滚动页面时,这些相交检测程序在滚动处理代码期间不断触发,从而导致用户对浏览器,网站及其计算机感到崩溃。
Intersection Observer API 使代码可以注册一个回调函数,该回调函数将在他们希望监视的元素进入或退出另一个元素(或视口)时,或者当两个相交的量改变请求的量时执行。这样,站点不再需要在主线程上执行任何操作来监视这种元素交集,并且浏览器可以自由地优化交集的管理。
Intersection Observer API不能告诉您的一件事:重叠的确切像素数或确切地说是重叠像素。但是,它涵盖了更常见的用例:“如果它们相交N%左右,我需要做点什么。”
Intersection observer 概念和使用
Intersection Observer API允许您配置一个 callback,只要一个元素(称为目标)与设备视口或指定元素相交,就会调用该 callback。 就此API而言,这称为根元素或根。 通常,您将需要注意与元素最接近的可滚动祖先相关的相交变化,或者,如果元素不是可滚动元素的后代,则要观察视口。 要注意相对于根元素的交点,请指定null。
无论您是使用视口还是其他元素作为根,API都以相同的方式工作,只要目标元素的可见性发生变化,以便与根交叉超过所需的交集量,就执行您提供的回调函数。
目标元素与其根之间的相交度为相交比。 这表示目标元素的百分比,该百分比可见为0.0到1.0之间的值。
创建一个 intersection observer
通过调用交集观察器的构造函数并向其传递一个回调函数来创建交集观察者,只要在一个方向或另一个方向上超过阈值,该回调函数便会运行:
1 | let options = { |
阈值1.0表示在root选项指定的元素中可见目标的100%时,将调用回调。
Intersection observer options 参数
传递给 IntersectionObserver()构造函数的 options 对象使您可以控制在哪些情况下调用观察者的回调。 它具有以下字段:
-
root
用作检查目标可见性的视口的元素。 必须是目标的祖先。 如果未指定或为null,则默认为浏览器视口。
-
rootMargin
围绕根的边距。可以具有类似于CSS
margin属性的值,例如"10px 20px 30px 40px"((上,右,下,左)。这些值可以是百分比。这组值用于在计算相交之前增大或缩小根元素边界框的每一侧。默认为全零。 -
threshold
一个数字或一个数字数组,指示观察者的回调应在目标可见性的百分比上执行。如果只想检测可见性何时超过50%标记,则可以使用0.5值。如果希望每次可见性再超过25%时都运行回调,则可以指定数组[0,0.25,0.5,0.75,1]。默认值为0(意味着即使可见一个像素,回调也将运行)。值为1.0意味着直到每个像素都可见,才认为阈值已通过。
定位要观察的元素
创建观察者后,需要给它一个目标元素以进行观察:
1 | let target = document.querySelector('#listItem'); |
每当目标达到为所指定的阈值时IntersectionObserver,就会调用回调。回调接收IntersectionObserverEntry对象列表和观察者:
1 | let callback = (entries, observer) => { |
请注意,您的回调是在主线程上执行的。它应尽快运行;如果需要完成任何耗时的操作,请使用Window.requestIdleCallback()。
另外,请注意,如果指定了该root选项,则目标必须是根元素的后代。
如何计算交集
Intersection Observer API考虑的所有区域都是矩形。形状不规则的元素被认为占据了包围元素所有部分的最小矩形。类似地,如果元素的可见部分不是矩形,则该元素的相交矩形被解释为包含该元素所有可见部分的最小矩形。
了解一点有关提供的各种属性如何IntersectionObserverEntry描述相交的方法很有用。
交点根和根边距
在跟踪元素与容器的交集之前,我们需要知道该容器是什么。该容器是交集根或根元素。这可以是文档中的特定元素(是要观察的元素的祖先),也null可以是文档的视口作为容器。
根的相交矩形是用于所要检查的目标或目标的矩形。该矩形的确定如下:
- 如果相交根是隐式根(即顶级
Document),则根相交矩形是视口的矩形。 - 如果相交根具有溢出剪辑,则根相交矩形是根元素的内容区域。
- 否则,根相交矩形是相交根的边界客户端矩形(通过调用
getBoundingClientRect()它返回)。
创建时,可以通过设置根边缘来进一步调整根相交矩形。定义偏移量中的值添加到相交根的边界框的每一侧,以创建最终的相交根边界(在执行回调时公开)。rootMarginIntersectionObserverrootMarginIntersectionObserverEntry.rootBounds
Thresholds 门槛
Intersection Observer API使用阈值,而不是报告可见的目标元素多少微小变化。创建观察者时,可以提供一个或多个数字值,这些数字值表示可见的目标元素的百分比。然后,API仅报告超过这些阈值的可见性更改。
例如,如果您希望每次目标的可见性通过每个25%标记向后或向前移动时都得到通知,则在创建观察者时,可以将数组[0,0.25,0.5,0.75,1]指定为阈值列表。您可以通过在可见性更改时检查传递给回调函数的isIntersecting属性值,来确定可见性的变化方向(即,元素变得更可见还是不可见)IntersectionObserverEntry。如果isIntersecting为true,则目标元素已变得至少与已通过的阈值一样可见。如果为false,则目标不再像给定阈值那样可见。
要了解阈值的工作原理,请尝试滚动下面的框。其中的每个彩色框都会显示其在四个角上都可见的百分比,因此您可以在滚动容器时看到这些比例随时间的变化。每个框都有不同的阈值集:
- 第一个框有一个针对每个可见度百分比的阈值;也就是说,
IntersectionObserver.thresholds数组是[0.00, 0.01, 0.02, ..., 0.99, 1.00]。 - 第二个框只有一个阈值,为50%。
- 第三个框的阈值是可见性的每10%(0%,10%,20%等)。
- 最后一个框的阈值各为25%。
裁剪和交点矩形
浏览器按以下方式计算最终的相交矩形:这一切都为您完成,但是了解这些步骤有助于更好地准确把握何时发生交叉点。
- 通过调用
getBoundingClientRect()目标,可以获得目标元素的边界矩形(即,完全包围组成该元素的每个组件的边界框的最小矩形)。这是最大的相交矩形。其余步骤将删除所有不相交的部分。 - 从目标的直接父块开始并向外移动,每个包含块的剪辑(如果有)都应用于相交矩形。根据两个块的交集和该
overflow属性指定的剪切模式(如果有)来确定块的剪切。设置overflow为其他任何值visible都会导致发生裁剪。 - 如果其中一个包含元素是嵌套浏览上下文的根(例如包含在中的文档)[
](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe),则交集矩形会被裁剪到包含上下文的视口,并且向上递归通过容器继续进行到容器的包含块。到达的最高层,将相交矩形剪切到框架的视口,然后框架的父元素是向相交根递归的下一个块。 - 当向上递归到达相交根时,生成的矩形将映射到相交根的坐标空间。
- 然后,通过与根相交矩形相交来更新生成的矩形。
- 最后,将此矩形映射到目标的坐标空间
document。
交叉变更callbacks
当在根元素中可见的目标元素的数量超过可见性阈值之一时,将IntersectionObserver执行对象的回调。回调接收所有IntersectionObserverEntry对象的数组作为输入,每个超过阈值的对象一个,以及对IntersectionObserver对象本身的引用。
阈值列表中的每个条目都是一个IntersectionObserverEntry对象,它描述一个被超过的阈值。也就是说,每个条目都描述了给定元素中有多少与根元素相交,该元素是否被认为相交以及过渡发生的方向。
下面的代码段显示了一个回调,该回调保留了元素从不相交根到相交至少75%过渡的次数的计数。
1 | intersectionCallback(entries) => { |
接口
Intersection Observer API的主要接口。提供用于创建和管理观察者的方法,该观察者可以监视相同交集配置的任意数量的目标元素。每个观察者可以异步观察一个或多个目标元素和共用祖先元素之间或与它们顶层的交点变化Document的视口。祖先或视口称为根。
描述在特定过渡时刻目标元素及其根容器之间的交集。只能以两种方式获得此类型的对象:作为IntersectionObserver回调的输入,或通过调用IntersectionObserver.takeRecords()。
一个简单的例子
这个简单的示例使目标元素在变得或多或少可见时更改其颜色和透明度。在使用Intersection Observer API的“计时元素可见性”中,您可以找到一个更广泛的示例,该示例显示如何计时用户可以看到一组元素(例如广告)多长时间,以及如何通过记录统计信息或更新元素来对该信息做出反应…
HTML
此示例的HTML非常简短,其中有一个主要元素,即我们将要定位的框(带有creative ID "box")和该框中的一些内容。
1 | <div id="box"> |
CSS
就本示例而言,CSS并不是十分重要。它对元素进行了布局,并确定background-colorand border属性可以参与CSS过渡,当元素或多或少被遮盖时,我们将使用它来影响元素的更改。
1 | #box { |
JavaScript
最后,让我们看一下使用Intersection Observer API进行事情的JavaScript代码。
配置
首先,我们需要准备一些变量并安装观察器。
1 | const numSteps = 20.0; |
我们在此处设置的常量和变量是:
-
numSteps一个常数,指示我们希望在0.0和1.0的可见性比率之间具有多少个阈值。
-
prevRatio此变量将用于记录上次超过阈值时可见性比率。这将让我们弄清楚目标元素是否变得越来越明显。
-
increasingColor定义可见性比率增加时将应用于目标元素的颜色的字符串。该字符串中的“比率”一词将替换为目标的当前可见性比率,因此该元素不仅会改变颜色,而且会变得越来越不透明,因为它变得越来越模糊。
-
decreasingColor同样,这是一个字符串,定义了可见率降低时将应用的颜色。
我们呼吁Window.addEventListener()开始收听load事件。一旦页面加载完成后,我们得到的元素的引用与ID "box"使用querySelector(),然后调用createObserver()我们将在稍后创建来处理建筑方法和安装的交叉点观测。
创建相交观察器
createObserver()一旦页面加载完成,便会调用该方法以处理实际创建新对象IntersectionObserver并开始观察目标元素的过程。
1 | function createObserver() { |
首先从设置一个options包含观察者设置的对象开始。我们要留意的目标元素相对于文档的视口的可见性的变化,所以root是null。我们不需要边距,因此边距偏移量rootMargin指定为“ 0px”。这使观察者可以观察目标元素的边界与视口边界之间的交集处的变化,而无需增加(或减去)任何空间。
可见度阈值列表threshold由函数构造buildThresholdList()。在此示例中,以编程方式构建阈值列表,因为存在许多阈值列表,并且该数量旨在调整。
一旦options准备好了,我们创建了新的观察员,调用IntersectionObserver()构造函数,指定一个函数被调用时,路口穿越我们的其中一个阈值,handleIntersect()和我们一组选项。然后observe(),我们调用返回的观察者,将所需的目标元素传递给它。
如果我们愿意的话,我们可以选择通过监视observer.observe()每个元素来监视多个元素是否相对于视口相交。
建立阈值比率数组
buildThresholdList()构建阈值列表的函数如下所示:
1 | function buildThresholdList() { |
这将构建阈值数组-通过将值介于1和之间的每个整数推i/numSteps入thresholds数组,每个阈值之间的比率为0.0和1.0 i之间numSteps。它还推0以包括该值。给定默认值numSteps(20),结果是以下阈值列表:
| # | Ratio | # | Ratio |
|---|---|---|---|
| 1个 | 0.05 | 11 | 0.55 |
| 2 | 0.1 | 12 | 0.6 |
| 3 | 0.15 | 13 | 0.65 |
| 4 | 0.2 | 14 | 0.7 |
| 5 | 0.25 | 15 | 0.75 |
| 6 | 0.3 | 16 | 0.8 |
| 7 | 0.35 | 17 | 0.85 |
| 8 | 0.4 | 18 | 0.9 |
| 9 | 0.45 | 19 | 0.95 |
| 10 | 0.5 | 20 | 1.0 |
当然,我们可以将阈值数组硬编码到我们的代码中,而这通常是您最终要做的。但是,此示例为添加配置控件以调整粒度提供了空间。
处理交集变更
当浏览器检测到目标元素(在我们的示例中为ID的元素"box")已经被公开或模糊,以致其可见性比率超过列表中的阈值之一时,它将调用处理程序函数handleIntersect():
1 | function handleIntersect(entries, observer) { |
对于IntersectionObserverEntry列表中的每个entries条目,我们查看条目intersectionRatio是否在上升;如果是,我们将目标的设置background-color为中的字符串increasingColor(请记住,它是"rgba(40, 40, 190, ratio)"),将单词“ ratio”替换为条目的intersectionRatio。结果:不仅颜色改变了,目标元素的透明度也改变了;当交叉比例降低时,背景色的Alpha值随之降低,从而使元素更透明。
同样,如果intersectionRatio下降,则使用字符串,decreasingColor并intersectionRatio在设置目标元素的之前将其中的“比率”一词替换为background-color。
最后,为了跟踪交叉比率是上升还是下降,我们记住变量中的当前比率prevRatio。
浏览器兼容性
Update compatibility data on GitHub
| Desktop | Mobile | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Chrome | Edge | Firefox | Internet Explorer | Opera | Safari | Android webview | Chrome for Android | Firefox for Android | Opera for Android | Safari on iOS | Samsung Internet | |
IntersectionObserverExperimental |
Full support51 | Full support15 | Full support55Open | No supportNo | Full supportYes | Full support12.1 | Full support51 | Full support51 | ? | ? | Full support12.2 | Full support5.0 |
IntersectionObserver() constructorExperimental |
Full support51 | Full support15 | Full support55Open | No supportNo | ? | Full support12.1 | Full support51 | Full support51 | ? | ? | Full support12.2 | Full support5.0 |
disconnectExperimental |
Full support51 | Full support15NotesOpen | Full support55Open | No supportNo | Full supportYes | ? | Full support51 | Full support51 | ? | ? | ? | Full support5.0 |
observeExperimental |
Full support51 | Full support15 | Full support55Open | No supportNo | Full supportYes | Full support12.1 | Full support51 | Full support51 | ? | ? | Full support12.2 | Full support5.0 |
rootExperimental |
Full support51 | Full support15 | Full support55Open | No supportNo | Full supportYes | Full support12.1 | Full support51 | Full support51 | ? | ? | Full support12.2 | Full support5.0 |
rootMarginExperimental |
Full support51 | Full support15 | Full support55Open | No supportNo | Full supportYes | Full support12.1NotesOpen | Full support51 | Full support51 | ? | ? | Full support12.2NotesOpen | Full support5.0 |
takeRecordsExperimental |
Full support51 | Full support15NotesOpen | Full support55Open | No supportNo | Full supportYes | ? | Full support51 | Full support51 | ? | ? | ? | Full support5.0 |
thresholdsExperimental |
Full support51 | Full support15 | Full support55Open | No supportNo | Full supportYes | Full support12.1 | Full support51 | Full support51 | ? | ? | Full support12.2 | Full support5.0 |
unobserveExperimental |
Full support51 | Full support15NotesOpen | Full support55Open | No supportNo | Full supportYes | Full support12.1 | Full support51 | Full support51 | ? | ? | Full support12.2 | Full support5.0 |