使用 JavaScript 的 Intersection Observer API 在滚动时显示内容
您是否曾经访问过这样的网站:当您向下滚动页面时,网页内容会随着您的接近而逐渐显现?不得不承认,这是一个非常巧妙的功能。您是否想过如何在不使用第三方库或插件的情况下在项目中实现此功能?JavaScript 原生的 Intersection Observer API 可以让您实现这一点……甚至更多。在本文中,我们将讨论 Intersection Observer API 的工作原理,以及如何通过构建一个实现“滚动时显示内容”功能的简单网页来使用它来检测元素的可见性。
先决条件
- JavaScript 的基础知识(初学者水平是可以接受的,因为我将像向 5 岁小孩解释一样详细地解释一切。:)
- HTML 和 CSS 的基本知识(您已经用它们构建了至少一个基本的网页)。
- 代码编辑器(推荐使用 VS Code)。
- 当然还有浏览器(推荐使用 Chrome 或 Firefox)。
什么是交叉口观察器?
Intersection Observer API 只是一种新方法,用于观察(监视)DOM 中元素相对于另一个根元素的位置和可见性,并在这些元素相交(相遇)时运行回调函数。
现在你可能会想,根元素到底是什么?其实,根元素就是其他元素的父元素或容器元素。也就是说,如果我们div
在 HTML 文档中创建了一个 元素,并在其中div
放置了一段p
文本,那么 元素div
就成为了该文本的直接根元素(父元素)p
,因为它包含了该段落。
<body>
<div>
<p>Lorem, ipsum.</p>
</div>
</body>
基于此逻辑,我们可以放心地说,body
也是 this 的直接父元素div
,也是p
text 的祖父元素。但你知道 DOM 中所有内容的祖先根元素是什么吗?查看 HTML 文档的浏览器会成为网页中任何区域的容器(根),该区域在任何时候都对浏览器的视口(屏幕)可见。
因此,本质上,Intersection Observer API 可用于观察元素,以查看该元素是否与其 DOM 中的根元素相交(相遇或穿过),或者它是否只是进入或离开浏览器的视口。并且观察者会在发生此事件时触发回调函数。
注意:回调函数只是一个普通函数,它作为另一个函数的参数(其参数的实际值)提供给该函数。
下面是我准备的一张图,展示了实际的交叉路口,它应该能让你了解它是如何工作的,但如果你仍然不清楚,不要担心......我会在一分钟内解释一切。
创建基本的 HTML/CSS 页面
现在我们知道了什么是交叉观察器 (Intersection Observer),让我们深入研究它的实现。我们将首先创建一个包含 3 个部分的简单 HTML 页面。第一部分和第三部分对我们来说不太重要,因为我们主要处理第二部分,我们只是想要更多的空间以便能够向下滚动页面。
<body>
<section class="section-1">
<h2>Section 1</h2>
</section>
<section class="section-2">
<img class="img" src="background.jpg" alt="" />
</section>
<section class="section-3">
<h2>Section 3</h2>
</section>
</body>
现在,在 CSS 部分,我们将为每个部分添加一个height
,100vh
并使用 使每个部分的内容居中flex
,然后为图像指定一个固定的响应式宽度,并通过应用背景色来分隔每个部分,使每个部分更加醒目。最后,我们将创建一个hidden
类,用于稍后使用 JavaScript 隐藏和显示内容。
h2 {
font-size: 3rem;
}
.img {
width: 95%;
max-width: 600px;
transition: all 1.5s ease-in;
}
section {
background-color: #dbe6eb;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.section-2 {
background-color: #fff;
}
.hidden {
opacity: 0;
transform: translateX(100%);
}
最终的布局👇 太好了,我们已经设置好了基本的网页,现在让我们深入研究 JavaScript 并讨论 Intersection Observer。
实现交叉口观察器 API
要使用交叉口观察器 API,我们需要首先使用其默认对象构造函数创建一个。
new IntersectionObserver();
这个构造函数基本上是 JavaScript 中的一个内置函数,负责创建一个观察者,然后我们可以使用它来观察我们的目标元素,这个构造函数有两个参数,接受两个参数。
第一个参数是一个回调函数,当与被观察元素发生交集时会调用该函数。还记得什么是回调函数吗?它只是一个普通函数,作为参数传递给另一个函数。所以,当发生交集时,交集观察器会得到一个要调用的函数。
第二个参数是一个包含自定义观察器选项的对象。此参数实际上是可选的,可以省略。如果不提供,观察器将使用其默认选项(稍后会详细介绍)。现在让我们创建一个交叉观察器。
首先,我们选择想要观察的目标元素。
const section = document.querySelector(‘.section-2’);
然后让我们创建一个观察者来观察这个section element
Const theObserver = IntersectionObserver(callbackFunction, options);
创建观察者后,我们必须使用观察者内置的observe()
方法告诉它要观察哪个目标元素。该方法接收要观察的目标元素作为参数。接下来我们就开始吧。
theObserver.observe(section);
让我们回顾一下刚才的操作。我们首先选择了一个要观察的目标元素section
,然后创建了一个观察者theObserver
,最后我们使用 方法来将要观察的目标元素传递给观察者,从而告诉观察者要观察什么observe()
。就这样,一切都设置好了,唯一的问题是我们既没有定义 our callbackFunction
,也没有定义options
object ,所以它们目前是未定义的。
选项对象
现在,让我们定义一下创建时传入观察器构造函数的选项,因为它目前还没有关联任何内容。我先定义选项对象(回想一下,这是用来自定义观察器的),然后解释里面的每个属性。
注意: 因为对象不能被提升(在定义之前使用),为了避免错误,它应该始终在传递给观察者之前在顶部定义,或者在创建观察者时将实际对象本身作为参数传递给*观察者*。
考虑到这一点,让我们按照适当的顺序重写迄今为止编写的 JavaScript 代码。
const section = document.querySelector(‘.section-2’);
const options = {
root: null,
threshold: 0.3,
rootMargin: "-100px",
}
const theObserver = new IntersectionObserver(callbackFunction, options);
}
theObserver.observe(section);
root
:在这里我们指定观察元素与哪个根元素相交。root
通常是目标元素在 DOM 中的祖先元素(即观察元素的容器或父元素)。null
如果我们希望观察元素与整个浏览器视口相交(这是默认值),则将值设置为 。可以将根元素视为观察目标元素需要与之接触的矩形“捕获框”。
threshold
:这threshold
基本上是观察目标在被视为相交之前应进入视野的百分比。困惑吗?那么,您是希望目标元素在触发回调之前完全进入视野(变为 100% 可见)?还是希望在运行回调之前只有一小部分在浏览器视口中可见?这就是您需要指定的内容threshold
。
-
阈值取值范围为 0 到 1 之间的数值,表示目标元素与根元素相交的百分比。例如,0.1 表示 10%,0.2 表示 20%,0.5 表示 50%,1 表示 100%。默认值为 0,表示只要被观察元素到达 0px
root
(即将进入视图),就会发生相交。 -
接收的值可以是单个值(这意味着您希望目标元素进行单个交集)或数组中的多个值(这意味着您希望目标元素进行多个交集并为每个交集运行回调)。
-
每次目标元素进入或离开根元素(视口)时,都会触发交集。这意味着,如果阈值为 0.1,则当元素的 10% 可见时会发生交集,而当元素离开视口时,又会发生另外 10% 的交集。
rootMargin
:因为根元素被视为具有四条边的矩形框架(边界框),所以可以像在 CSS 中一样将边距(正或负)应用于根元素,以增大或缩小其相交框架。
回想一下,浏览器的视口就是我们的root
元素(一个矩形框),我们将阈值设置为0.3
?,这意味着当观察元素的 30% 进入视野时,应该发生相交。现在,我们还将 设置为rootMargin
,-100px
这将使相交范围缩小 -100px,相交将不再发生在我们指定的范围30% threshold
,而是等到目标元素在最初的 30% 之后进入视野,再进入 100px(可以将其视为在 30% 的基础上加上 100px)。
如果将边距设置为,100px
则在观察到的元素仍100px
远离 30% 阈值时会触发交集(负边距会缩小交集框架,而正边距会增大/将其向外推)。
回调函数
现在我们可以定义回调函数了,这是最后一个难题。我们先定义一下这个函数,但暂时不会对它做任何操作,因为我们首先需要了解一下 Intersection Observer 的行为以及它的实际工作方式。
当一个带有交集观察器的网页首次加载时,无论是否存在实际的交集,观察器都会默认触发一次提供的回调函数(我知道,这有点奇怪)。当这种情况发生时,观察器会将一个entries
数组传递给回调函数,而该entries
数组本身包含一个IntersectionObserverEntry
对象。该对象包含几个属性,用于描述目标元素与其根容器之间的交集。
说得够多了...让我们定义回调函数,以便我们可以看到对象本身。
function callbackFunction(entries) {
console.log(entries);
}
我们定义了回调函数,并entries
为观察者提供了一个参数,用于传递其观察结果。当回调函数触发时,我们会将传入参数的实际参数打印到控制台。现在,如果我们加载网站并打开开发工具,就会看到以下界面👇
正如您在开发工具中看到的那样,条目包含有关交叉点的几个详细信息,您可以自行探索每个属性,但在本文中,我们仅关注以下内容:
-
target:
这是观察者正在观察的与根元素相交的实际元素。 -
isIntersecting:
如果被观察的目标元素当前与根元素相交(如果目标元素的阈值已相交)或者不相交,则返回布尔值 true。false
-
isVisible:
这将返回一个布尔值true
或,false
表示被观察的目标元素当前是否在浏览器的视口中可见。
现在我们了解了这些属性包含的返回值,我们现在可以编写一个适当的函数来检查条目对象,以查看我们的目标元素是否与浏览器的视口相交,并使用该信息执行某些操作。
但在进行回调之前,让我们选择我们希望在 Intersection 上显示的内容。
const imgContent = document.querySelector(".img");
现在让我们先定义回调,然后逐行进行。
function callBackFunction(entries) {
const [entry] = entries;
if (entry.isIntersecting) {
imgContent.classList.remove("hidden");
} else {
imgContent.classList.add("hidden");
}
}
现在让我们逐行剖析该函数。
const [entry] = entries
:还记得吗?观察者会将一个包含 IntersectionObserverEntry 对象的条目数组传递给回调函数。我只是简单地解构了数组(提取了其中的对象)并将其存储在一个entry
变量中,以便更容易地直接访问存储在该对象中的相关属性。
if(entry.isIntersecting) {imgContent.classList.remove("hidden")}
:之后,我们检查isIntersecting
属性以查看我们的目标元素(目标部分 2)是否与视口相交,如果值是,true
我们从图像中删除hidden
我们最初在 CSS 中创建的类以显示它(您可能想知道为什么我们要删除一个我们从未添加到图像中的隐藏类...下面的 else 块就是您的答案)。
else {imgContent.classList.add("hidden")}
:否则,如果isIntersecting
值为,false
我们将hidden
类添加到图像中。但是,您还记得吗?当我们加载网页时,观察者会触发一次回调函数?当发生这种情况时,初始条目会传递给我们的函数。由于没有交集,这个 else 代码块将会运行,从而在加载时隐藏我们的图像。
请原谅录制延迟,我的屏幕录像机出问题了。但正如您所见,当我们向观察区域滚动时,一旦元素的 30% 进入视野,我们应该会得到一个交集,但由于我们将 rootMargin 设置为 ,所以-100px
当目标区域再次滚动 100px 进入视野时,交集才会发生,然后触发交集并触发回调。然后,图像会显示出来,并在隐藏类被移除后滑回其原始位置。
当被观察的部分滚动出视图(退出)时,观察者会再次触发回调,如果您还记得的话,我们讨论了观察者如何在进入时触发,并在退出视口时再次触发……并且由于第二次没有实际的交集,因此当我们滚动出视图时,隐藏类会再次添加并且图像会被隐藏。
这是我们编写的完整 JavaScript 代码。
const section = document.querySelector(".section-2");
const imgContent = document.querySelector(".img");
const objOptions = {
root: null,
threshold: 0.3,
rootMargin: "-100px",
};
const sectionObserver = new IntersectionObserver(callBackFunction, objOptions);
sectionObserver.observe(section);
function callBackFunction(entries) {
const [entry] = entries;
console.log(entry);
if (entry.isIntersecting) {
imgContent.classList.remove("hidden");
} else {
imgContent.classList.add("hidden");
}
}
结论
恭喜!!!你已经成功实现了一个基本的交集,但由于文章篇幅过长,还有一些内容我没有讲到。我们没有讲解如何观察多个元素,也没有讨论如何在交集之后取消观察元素。为此,我将在本文的第二部分中讲解这些内容,并构建另一个网页,用于观察多个部分,并针对它们各自的交集执行相应的操作。
如果你想知道 IntersectionObserver 还能做什么,你的想象力是无限的,你可以实现无限滚动页面、延迟加载功能、粘性菜单等等。我可能会制作更多涵盖这些功能的教程,敬请期待。
请我喝杯咖啡☕
如果您喜欢我的文章并发现它有用,您可以使用以下任何链接给我买杯咖啡。👇
另外,请点赞并关注以获取更多内容。如果您有任何疑问或发现任何错误……请留下反馈,因为这是我的第一篇技术文章。