使用 Intersection Observer API 测量元素可见性时间

在本文中,我们将构建一个模拟博客,其中包含一些散布在页面内容中的广告,然后使用 Intersection Observer API 来跟踪每个广告对用户可见的时间。当广告的可见时间超过一分钟时,它将被替换为新的广告。

尽管此示例的许多方面与实际使用情况不符(特别是,所有文章都具有相同的文本且未从数据库加载,并且只有少数从数组中选择的简单纯文本广告),但这应该足以理解该 API,从而快速学习如何将 Intersection Observer API 应用到您自己的网站。

此示例中使用跟踪广告可见性的概念有一个很好的理由。事实证明,Web 上广告中使用 Flash 或其他脚本最常见的用途之一是记录每个广告的可见时间,以便进行计费和收入支付。如果没有 Intersection Observer API,这将通过为每个单独的广告使用间隔和超时或其他可能使页面变慢的技术来完成。使用此 API 可以让浏览器精简一切,从而大大减少对性能的影响。

让我们开始吧!

构建网站

网站结构:HTML

网站的结构并不太复杂。我们将使用 CSS Grid 来样式化和布局网站,因此我们可以直接在这里进行

html
<div class="wrapper">
  <header>
    <h1>A Fake Blog</h1>
    <h2>Showing Intersection Observer in action!</h2>
  </header>

  <aside>
    <nav>
      <ul>
        <li><a href="#link1">A link</a></li>
        <li><a href="#link2">Another link</a></li>
        <li><a href="#link3">One more link</a></li>
      </ul>
    </nav>
  </aside>

  <main>…</main>
</div>

这是整个网站的框架。顶部是网站的页眉区域,包含在 <header> 块中。在此下方,我们将网站的侧边栏定义为 <aside> 块中链接的列表。

最后是主体内容。我们从一个空的 <main> 元素开始。此框将在以后使用脚本填充。

使用 CSS 样式化网站

定义了网站的结构后,我们转向网站的样式。让我们单独查看页面每个组件的样式。

基础知识

我们为 <body><main> 元素提供样式,以定义网站的背景以及网站各个部分将放置在其中的网格。

css
body {
  font-family: "Open Sans", "Helvetica", "Arial", sans-serif;
  background-color: aliceblue;
}

.wrapper {
  display: grid;
  grid-template-columns: auto minmax(min-content, 1fr);
  grid-template-rows: auto minmax(min-content, 1fr);
  max-width: 700px;
  margin: 0 auto;
  background-color: aliceblue;
}

这里配置了网站的 <body>,以使用几种常见的无衬线字体之一,并使用 "aliceblue" 作为背景颜色。然后定义了 "wrapper" 类;它包含整个博客,包括页眉、侧边栏和主体内容(文章和广告)。

包装器建立了一个包含两列两行的 CSS 网格。第一列(根据其内容自动调整大小)用于侧边栏,第二列(将用于主体内容)的大小至少为列内容宽度,最多为所有剩余可用空间。

第一行将专门用于网站页眉。行的尺寸与列的尺寸相同:第一行自动调整大小,第二行使用剩余空间,但至少足以容纳其中所有元素。

包装器的宽度固定为 700px,以便在 MDN 下方内联显示时可以容纳在可用空间中。

页眉

页眉相当简单,因为在此示例中它只包含一些文本。它的样式如下

css
header {
  grid-column: 1 / -1;
  grid-row: 1;
  background-color: aliceblue;
}

grid-row 设置为 1,因为我们希望页眉放置在网站网格的顶部行。更有趣的是我们在此处使用 grid-column;在这里我们指定我们希望列从第一列开始,并在最后一条网格线之后的第一列结束——换句话说,页眉跨越网格中的所有列。非常适合我们的需求。

侧边栏

我们的侧边栏用于显示网站上其他页面的链接。在此示例中它们都无效,但它们的存在是为了帮助呈现类似博客的体验。侧边栏使用 <aside> 元素表示,样式如下

css
aside {
  grid-column: 1;
  grid-row: 2;
  background-color: cornsilk;
  padding: 5px 10px;
}

aside ul {
  padding-left: 0;
}

aside ul li {
  list-style: none;
}

aside ul li a {
  text-decoration: none;
}

这里最重要的一点是 grid-column 设置为 1,将侧边栏放置在屏幕的左侧。如果将其更改为 -1,它将显示在右侧(尽管其他一些元素需要调整其边距以使间距恰到好处)。grid-row 设置为 2,将其放置在网站主体旁边。

内容主体

说到网站主体:网站的主要内容保存在 <main> 元素中。以下样式应用于它

css
main {
  grid-column: 2;
  grid-row: 2;
  margin: 0;
  margin-left: 16px;
  font-size: 16px;
}

这里的主要特点是网格位置设置为将主体内容放置在第 2 列、第 2 行。

文章

每篇文章都包含在 <article> 元素中,样式如下

css
article {
  background-color: white;
  padding: 6px;
}

article:not(:last-child) {
  margin-bottom: 8px;
}

article h2 {
  margin-top: 0;
}

这会创建带有白色背景的文章框,它们漂浮在蓝色背景之上,文章周围有一个小边距。容器中不是最后一项的每篇文章都有一个 8px 的下边距以分隔事物。

广告

最后,广告具有以下初始样式。个别广告可能会在一定程度上自定义样式,我们稍后会看到。

css
.ad {
  height: 96px;
  padding: 6px;
  border-color: #555555;
  border-style: solid;
  border-width: 1px;
}

.ad:not(:last-child) {
  margin-bottom: 8px;
}

.ad h2 {
  margin-top: 0;
}

.ad div {
  position: relative;
  float: right;
  padding: 0 4px;
  height: 20px;
  width: 120px;
  font-size: 14px;
  bottom: 30px;
  border: 1px solid black;
  background-color: rgb(255 255 255 / 50%);
}

这里没什么神奇的。这是相当基本的 CSS。

用 JavaScript 将它们连接起来

这使我们进入了使一切工作的 JavaScript 代码。让我们从全局变量开始

js
const contentBox = document.querySelector("main");

let nextArticleID = 1;
let visibleAds = new Set();
let previouslyVisibleAds = null;

它们的使用方式如下

contentBox

对 DOM 中 <main> 元素的 HTMLElement 对象的引用。我们将在此处插入文章和广告。

nextArticleID

每篇文章都分配一个唯一的 ID 号;此变量跟踪要使用的下一个 ID,从 1 开始。

visibleAds

我们将用于跟踪当前屏幕上可见广告的 Set

previouslyVisibleAds

用于在文档不可见时(例如,如果用户已切换到另一个页面)临时存储可见广告列表。

设置

为了进行设置,我们在页面加载时运行以下代码

js
document.addEventListener("visibilitychange", handleVisibilityChange);

const observerOptions = {
  root: null,
  rootMargin: "0px",
  threshold: [0.0, 0.75],
};
const adObserver = new IntersectionObserver(
  intersectionCallback,
  observerOptions,
);
const refreshIntervalID = setInterval(handleRefreshInterval, 1000);

const loremIpsum =
  "<p>Lorem ipsum dolor sit amet, consectetur adipiscing" +
  " elit. Cras at sem diam. Vestibulum venenatis massa in tincidunt" +
  " egestas. Morbi eu lorem vel est sodales auctor hendrerit placerat" +
  " risus. Etiam rutrum faucibus sem, vitae mattis ipsum ullamcorper" +
  " eu. Donec nec imperdiet nibh, nec vehicula libero. Phasellus vel" +
  " malesuada nulla. Aliquam sed magna aliquam, vestibulum nisi at," +
  " cursus nunc.</p>";

buildContents();

首先,我们为 visibilitychange 事件设置一个事件监听器。当文档变为隐藏或可见时(例如,当用户在浏览器中切换标签时),会发送此事件。Intersection Observer API 在检测交集时不会考虑这一点,因为交集不受页面可见性的影响。因此,当页面被选项卡化时,我们需要暂停计时器;因此,此事件监听器。

接下来,我们为 IntersectionObserver 设置选项,它将监视目标元素(在我们的例子中是广告)相对于文档的交集变化。选项配置为监视与文档视口的交集(通过将 root 设置为 null)。我们没有边距来扩展或收缩交集根矩形;我们希望在交集目的上精确匹配文档视口的边界。并且 threshold 设置为一个包含值 0.0 和 0.75 的数组;这将导致我们的回调在目标元素完全被遮挡或首次开始变得不被遮挡(交集比率 0.0)或在任一方向上通过 75% 可见(交集比率 0.75)时执行。

通过调用 IntersectionObserver 的构造函数,传入回调函数 intersectionCallback 和我们的选项,创建观察者 adObserver

变量 loremIpsum 包含我们将用于所有文章正文的文本。显然,在现实世界中,您会有一段代码从数据库或其他类似来源获取文章,但这足以满足我们的目的。每篇文章都使用相同的文本;您当然可以轻松更改它。

然后我们调用函数 buildContents(),我们稍后将定义它来实际生成并将我们想要呈现的文章和广告插入到文档中。

最后,我们设置了一个每秒触发一次的间隔来处理任何必要的刷新。我们需要每秒刷新一次,因为在此示例中,我们正在所有可见广告中显示计时器。您可能根本不需要间隔,或者您可能会以不同的方式或使用不同的时间间隔进行操作。

处理文档可见性更改

让我们看看 visibilitychange 事件的处理程序。当文档本身变得可见或不可见时,我们的脚本会收到此事件。这里最重要的场景是当用户切换选项卡时。由于 Intersection Observer 只关心目标元素和交集根之间的交集,而不关心选项卡的可见性(这是一个完全不同的问题),我们需要使用 Page Visibility API 来检测这些选项卡切换并在期间禁用我们的计时器。

js
function handleVisibilityChange() {
  if (document.hidden) {
    if (!previouslyVisibleAds) {
      previouslyVisibleAds = visibleAds;
      visibleAds = new Set();
      previouslyVisibleAds.forEach((adBox) => {
        updateAdTimer(adBox);
        adBox.dataset.lastViewStarted = 0;
      });
    }
  } else {
    previouslyVisibleAds.forEach((adBox) => {
      adBox.dataset.lastViewStarted = performance.now();
    });
    visibleAds = previouslyVisibleAds;
    previouslyVisibleAds = null;
  }
}

由于事件本身没有说明文档是从可见状态切换到不可见状态还是反之,因此会检查 document.hidden 属性以查看文档当前是否不可见。由于理论上可能会多次调用,因此我们只有在我们尚未暂停计时器并保存现有广告的可见性状态时才继续。

要暂停计时器,我们只需从可见广告集 (visibleAds) 中删除广告并将其标记为非活动。为此,我们首先将可见广告集保存到一个名为 previouslyVisibleAds 的变量中,以确保当用户选项卡回到文档时我们可以恢复它们,然后我们清空 visibleAds 集,以便它们不会被视为可见。然后,对于每个被暂停的广告,我们调用 updateAdTimer() 函数,该函数处理更新广告的总可见时间计数器,然后我们将它们的 dataset.lastViewStarted 属性设置为 0,这表示选项卡的计时器未运行。

如果文档刚刚变得可见,我们反转此过程:首先我们遍历 previouslyVisibleAds 并使用 performance.now() 方法将每个广告的 dataset.lastViewStarted 设置为当前文档的时间(自文档创建以来的毫秒数)。然后我们将 visibleAds 重新设置为 previouslyVisibleAds 并将后者设置为 null。现在所有广告都已重新启动,并配置为知道它们在当前时间变得可见,以便在下次更新时它们不会累加页面被选项卡关闭的时间长度。

处理交集变化

在浏览器事件循环的每次通过中,每个 IntersectionObserver 都会检查其任何目标元素是否已通过观察者的任何交集比率阈值。对于每个观察者,都会编译一个已通过这些阈值的目标列表,并作为 IntersectionObserverEntry 对象的数组发送到观察者的回调。我们的回调函数 intersectionCallback() 如下所示

js
function intersectionCallback(entries) {
  entries.forEach((entry) => {
    const adBox = entry.target;

    if (entry.isIntersecting) {
      if (entry.intersectionRatio >= 0.75) {
        adBox.dataset.lastViewStarted = entry.time;
        visibleAds.add(adBox);
      }
    } else {
      visibleAds.delete(adBox);
      if (
        entry.intersectionRatio === 0.0 &&
        adBox.dataset.totalViewTime >= 60000
      ) {
        replaceAd(adBox);
      }
    }
  });
}

如前所述,IntersectionObserver 回调接收一个数组作为输入,该数组包含所有已变得比交集观察者比率更可见或更不可见的观察者目标元素。我们遍历每个这些条目——它们是 IntersectionObserverEntry 类型。如果目标元素与根相交,我们就知道它刚刚从遮挡状态过渡到可见状态。如果它变得至少 75% 可见,那么我们认为广告可见,我们通过将广告的 dataset.lastViewStarted 属性设置为 entry.time 中的过渡时间来启动计时器,然后将广告添加到 visibleAds 集中,以便我们知道随着时间的推移对其进行处理。

如果广告已转换为非相交状态,我们从可见广告集中删除该广告。然后我们有一个特殊行为:我们查看 entry.intersectionRatio 是否为 0.0;如果是,则表示该元素已完全被遮挡。如果是这种情况,并且广告总共可见至少一分钟,我们调用一个我们稍后将创建的名为 replaceAd() 的函数,用一个新的广告替换现有广告。这样,用户随着时间的推移会看到各种广告,但广告只有在不可见时才会被替换,从而获得流畅的体验。

处理定期操作

我们的间隔处理程序 handleRefreshInterval() 大约每秒调用一次,这要归功于在 上面描述的 startup() 函数中对 setInterval() 的调用。它的主要工作是每秒更新计时器并安排重绘以更新我们将在每个广告中绘制的计时器。

js
function handleRefreshInterval() {
  const redrawList = [];

  visibleAds.forEach((adBox) => {
    const previousTime = adBox.dataset.totalViewTime;
    updateAdTimer(adBox);

    if (previousTime !== adBox.dataset.totalViewTime) {
      redrawList.push(adBox);
    }
  });

  if (redrawList.length) {
    window.requestAnimationFrame((time) => {
      redrawList.forEach((adBox) => {
        drawAdTimer(adBox);
      });
    });
  }
}

数组 redrawList 将用于维护在此刷新周期内需要重绘的所有广告的列表,因为它可能不完全与由于系统活动或您已将间隔设置为除 1000 毫秒以外的其他值而经过的时间相同。

然后,对于每个可见的广告,我们保存 dataset.totalViewTime 的值(截至上次更新时广告已可见的总毫秒数),然后调用 updateAdTimer() 来更新时间。如果它已更改,那么我们将广告推送到 redrawList 上,以便我们知道它需要在下一个动画帧期间进行更新。

最后,如果至少有一个元素需要重绘,我们使用 requestAnimationFrame() 来调度一个函数,该函数将在下一个动画帧期间重绘 redrawList 中的每个元素。

更新广告的可见性计时器

以前(参见 处理文档可见性更改处理定期操作),我们已经看到,当我们需要更新广告的“总可见时间”计数器时,我们调用一个名为 updateAdTimer() 的函数来执行此操作。此函数将广告的 HTMLDivElement 对象作为输入。代码如下

js
function updateAdTimer(adBox) {
  const lastStarted = adBox.dataset.lastViewStarted;
  const currentTime = performance.now();

  if (lastStarted) {
    const diff = currentTime - lastStarted;

    adBox.dataset.totalViewTime =
      parseFloat(adBox.dataset.totalViewTime) + diff;
  }

  adBox.dataset.lastViewStarted = currentTime;
}

为了跟踪元素的可见时间,我们在每个广告上使用两个自定义数据属性(参见 data-*

lastViewStarted

广告可见性计数上次更新或广告上次变得可见的时间(以毫秒为单位,相对于文档创建时间)。如果广告在上次检查时不可见,则为 0。

totalViewTime

广告已可见的总毫秒数。

这些通过每个广告的 HTMLElement.dataset 属性访问,该属性提供了一个 DOMStringMap,将每个自定义属性的名称映射到其值。这些值是字符串,但我们可以轻松地将它们转换为数字——事实上,JavaScript 通常会自动完成此操作,尽管我们将在一个实例中必须自己完成。

我们首先将广告之前的可见性状态检查时间 (adBox.dataset.lastViewStarted) 的值获取到一个名为 lastStarted 的局部变量中。我们还使用 performance.now() 将当前的“自创建以来时间”值获取到 currentTime 中。

如果 lastStarted 不为零——这意味着计时器当前正在运行,我们计算当前时间与开始时间之间的差值,以确定计时器自上次可见以来已可见的毫秒数。这被添加到广告 totalViewTime 的当前值中,以使总数保持最新。请注意此处使用 parseFloat();因为这些值是字符串,如果没有它,JavaScript 会尝试进行字符串连接而不是加法。

最后,广告的上次查看时间将更新为当前时间。无论广告在此函数调用时是否正在运行,都会执行此操作;这导致广告的计时器在此函数返回时始终运行。这很有意义,因为此函数仅在广告可见时才调用,即使它刚刚变得可见。

绘制广告的计时器

在每个广告内部,出于演示目的,我们绘制了其 totalViewTime 的当前值,并将其转换为分钟和秒。这通过将广告的元素传递到 drawAdTimer() 函数来处理

js
function drawAdTimer(adBox) {
  const timerBox = adBox.querySelector(".timer");
  const totalSeconds = adBox.dataset.totalViewTime / 1000;
  const sec = Math.floor(totalSeconds % 60);
  const min = Math.floor(totalSeconds / 60);

  timerBox.innerText = `${min}:${sec.toString().padStart(2, "0")}`;
}

此代码使用其 ID "timer" 查找广告的计时器,并通过将广告的 totalViewTime 除以 1000 来计算经过的秒数。然后计算经过的分钟数和秒数,然后将计时器的 innerText 设置为表示该时间的字符串,格式为 m:ss。String.padStart() 方法用于确保如果秒数小于 10,则将其填充为两位数。

构建页面内容

buildContents() 函数由 启动代码 调用,用于选择要呈现的文章和广告并将其插入文档中

js
function buildContents() {
  for (let i = 0; i < 5; i++) {
    contentBox.appendChild(createArticle(loremIpsum));

    if (!(i % 2)) {
      loadRandomAd();
    }
  }
}

buildContents() 创建一个包含五篇文章的页面。在每个奇数文章之后,“加载”一个广告并将其插入页面。文章使用一个名为 createArticle() 的方法创建后插入到内容框中(即包含所有网站内容的 <main> 元素),我们将在后面查看该方法。

广告是使用一个名为 loadRandomAd() 的函数创建的,该函数既创建广告又将其插入页面。我们稍后会看到这个相同的函数也可以替换现有广告,但目前,我们正在将广告附加到现有内容中。

创建文章

为了创建文章的 <article> 元素(以及其所有内容),我们使用 createArticle() 函数,该函数接受一个字符串作为输入,该字符串是文章的完整文本,用于添加到页面。

js
function createArticle(contents) {
  const articleElem = document.createElement("article");
  articleElem.id = nextArticleID;

  const titleElem = document.createElement("h2");
  titleElem.innerText = `Article ${nextArticleID} title`;
  articleElem.appendChild(titleElem);

  articleElem.innerHTML += contents;
  nextArticleID += 1;

  return articleElem;
}

首先,创建 <article> 元素并将其 ID 设置为唯一值 nextArticleID(从 1 开始,每个文章递增)。然后我们为文章标题创建并附加一个 h2 元素,然后将 contents 中的 HTML 附加到它。最后,nextArticleID 递增(以便下一个元素获得新的唯一 ID),我们将新的 <article> 元素返回给调用者。

创建广告

loadRandomAd() 函数模拟加载广告并将其添加到页面。如果您不为 replaceBox 传递值,则会创建一个新元素来包含广告;然后将广告附加到页面。如果您指定 replaceBox,则该框被视为现有广告元素;不是创建一个新的,而是更改现有元素以包含新广告的样式、内容和其他数据。这避免了在更新广告时进行冗长的布局工作的风险,这可能会在您首先删除旧元素然后插入新元素时发生。

js
function loadRandomAd(replaceBox) {
  const ads = [
    {
      bgcolor: "#cceecc",
      title: "Eat Green Beans",
      body: "Make your mother proud—they're good for you!",
    },
    {
      bgcolor: "aquamarine",
      title: "MillionsOfFreeBooks.whatever",
      body: "Read classic literature online free!",
    },
    {
      bgcolor: "lightgrey",
      title: "3.14 Shades of Gray: A novel",
      body: "Love really does make the world go round…",
    },
    {
      bgcolor: "#ffeeee",
      title: "Flexbox Florist",
      body: "When life's layout gets complicated, send flowers.",
    },
  ];
  let adBox, title, body, timerElem;

  const ad = ads[Math.floor(Math.random() * ads.length)];

  if (replaceBox) {
    adObserver.unobserve(replaceBox);
    adBox = replaceBox;
    title = replaceBox.querySelector(".title");
    body = replaceBox.querySelector(".body");
    timerElem = replaceBox.querySelector(".timer");
  } else {
    adBox = document.createElement("div");
    adBox.className = "ad";
    title = document.createElement("h2");
    body = document.createElement("p");
    timerElem = document.createElement("div");
    adBox.appendChild(title);
    adBox.appendChild(body);
    adBox.appendChild(timerElem);
  }

  adBox.style.backgroundColor = ad.bgcolor;

  title.className = "title";
  body.className = "body";
  title.innerText = ad.title;
  body.innerHTML = ad.body;

  adBox.dataset.totalViewTime = 0;
  adBox.dataset.lastViewStarted = 0;

  timerElem.className = "timer";
  timerElem.innerText = "0:00";

  if (!replaceBox) {
    contentBox.appendChild(adBox);
  }

  adObserver.observe(adBox);
}

首先是数组 ads。此数组包含创建每个广告所需的数据。我们这里有四个随机选择。在现实世界场景中,当然,广告将来自数据库,或者更可能来自您使用 API 获取广告的广告服务。但是,我们的需求很简单:每个广告都由一个具有三个属性的对象表示:背景颜色 (bgcolor)、标题 (title) 和正文文本字符串 (body)。

然后我们定义几个变量

adBox

这将设置为表示广告的元素。对于附加到页面的新广告,这使用 Document.createElement() 创建。替换现有广告时,此项设置为指定的广告元素 (replaceBox)。

title

将保存表示广告标题的 h2 元素。

body

将保存表示广告正文文本的 <p>

timerElem

将保存包含广告迄今为止可见时间的 <div> 元素。

通过计算 Math.floor(Math.random() * ads.length) 来选择一个随机广告;结果是 0 到广告数量减一之间的值。相应的广告现在称为 adBox

如果为 replaceBox 指定了值,我们将其用作广告元素。为此,我们首先通过调用 IntersectionObserver.unobserve() 来结束对元素的观察。然后,构成广告的每个元素的局部变量:广告框本身、标题、正文和计时器框,都设置为现有广告中的相应元素。

如果没有为 replaceBox 指定值,我们创建一个新的广告元素。广告的新 <div> 元素被创建,并通过将其类名设置为 "ad" 来建立其属性。接下来,创建广告标题元素,以及正文和可见性计时器;它们分别是 h2<p><div> 元素。这些元素被附加到 adBox 元素。

之后,代码路径再次汇合。广告的背景颜色设置为新广告记录中指定的值,元素的类和内容也相应设置。

接下来,是时候通过将 adBox.dataset.totalViewTimeadBox.dataset.lastViewStarted 设置为 0 来设置自定义数据属性以跟踪广告的可见性数据了。

最后,我们设置将显示计时器的 <div> 的 ID,该计时器将显示广告可见了多长时间,并将其类设置为 "timer"。初始文本设置为“0:00”,表示起始时间为 0 分 0 秒,并将其附加到广告中。

如果我们没有替换现有广告,我们需要使用 Document.appendChild() 将元素附加到页面的内容区域。如果我们要替换广告,它已经存在,其内容已替换为新广告的内容。然后我们对 Intersection Observer adObserver 调用 observe() 方法,开始监视广告与视口交集的变化。从现在开始,每当广告变得 100% 被遮挡或甚至一个像素变得可见,或者广告以某种方式通过 75% 可见时,都会执行观察者的回调

替换现有广告

我们的观察者回调会关注那些被 100% 遮挡且总可见时间至少为一分钟的广告。当这种情况发生时,将调用 replaceAd() 函数,并以该广告的元素作为输入,以便可以用新广告替换旧广告。

js
function replaceAd(adBox) {
  updateAdTimer(adBox);

  const visibleTime = adBox.dataset.totalViewTime;
  console.log(
    `Replacing ad: ${
      adBox.querySelector("h2").innerText
    } - visible for ${visibleTime}`,
  );

  loadRandomAd(adBox);
}

replaceAd() 首先在现有广告上调用 updateAdTimer(),以确保其计时器是最新的。这确保了当我们读取其 totalViewTime 时,我们看到广告对用户可见的确切最终值。然后我们报告该数据;在这种情况下,通过将其记录到控制台,但在现实世界中,您会将信息提交到广告服务的 API 或将其保存到数据库中。

然后我们通过调用 loadRandomAd() 加载新广告,将要替换的广告指定为输入参数。正如我们之前看到的,如果您将现有广告的元素指定为输入参数,loadRandomAd() 将用与新广告对应的内容和数据替换现有广告。

新广告的元素对象将返回给调用者,以备需要。

结果

结果页面如下所示。尝试通过上下滚动进行实验,并注意可见性的变化如何影响每个广告中的计时器。还要注意,每个广告在可见一分钟后都会被替换(但广告必须首先滚动出视图并再次滚动回来),以及当文档被选项卡到后台时计时器如何暂停。但是,用另一个窗口覆盖浏览器不会暂停计时器。

另见