使用 Intersection Observer API 測量元素可見性時間
在本文中,我們將構建一個模擬部落格,其中包含一些散佈在頁面內容中的廣告,然後使用 Intersection Observer API 來跟蹤每個廣告對使用者可見的時間。當廣告的可見時間超過一分鐘時,它將被替換為新的廣告。
儘管此示例的許多方面與實際使用情況不符(特別是,所有文章都具有相同的文字且未從資料庫載入,並且只有少數從陣列中選擇的簡單純文字廣告),但這應該足以理解該 API,從而快速學習如何將 Intersection Observer API 應用到您自己的網站。
此示例中使用跟蹤廣告可見性的概念有一個很好的理由。事實證明,Web 上廣告中使用 Flash 或其他指令碼最常見的用途之一是記錄每個廣告的可見時間,以便進行計費和收入支付。如果沒有 Intersection Observer API,這將透過為每個單獨的廣告使用間隔和超時或其他可能使頁面變慢的技術來完成。使用此 API 可以讓瀏覽器精簡一切,從而大大減少對效能的影響。
讓我們開始吧!
構建網站
網站結構:HTML
網站的結構並不太複雜。我們將使用 CSS Grid 來樣式化和佈局網站,因此我們可以直接在這裡進行
<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> 元素提供樣式,以定義網站的背景以及網站各個部分將放置在其中的網格。
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 下方內聯顯示時可以容納在可用空間中。
頁首
頁首相當簡單,因為在此示例中它只包含一些文字。它的樣式如下
header {
grid-column: 1 / -1;
grid-row: 1;
background-color: aliceblue;
}
grid-row 設定為 1,因為我們希望頁首放置在網站網格的頂部行。更有趣的是我們在此處使用 grid-column;在這裡我們指定我們希望列從第一列開始,並在最後一條網格線之後的第一列結束——換句話說,頁首跨越網格中的所有列。非常適合我們的需求。
側邊欄
我們的側邊欄用於顯示網站上其他頁面的連結。在此示例中它們都無效,但它們的存在是為了幫助呈現類似部落格的體驗。側邊欄使用 <aside> 元素表示,樣式如下
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> 元素中。以下樣式應用於它
main {
grid-column: 2;
grid-row: 2;
margin: 0;
margin-left: 16px;
font-size: 16px;
}
這裡的主要特點是網格位置設定為將主體內容放置在第 2 列、第 2 行。
文章
每篇文章都包含在 <article> 元素中,樣式如下
article {
background-color: white;
padding: 6px;
}
article:not(:last-child) {
margin-bottom: 8px;
}
article h2 {
margin-top: 0;
}
這會建立帶有白色背景的文章框,它們漂浮在藍色背景之上,文章周圍有一個小邊距。容器中不是最後一項的每篇文章都有一個 8px 的下邊距以分隔事物。
廣告
最後,廣告具有以下初始樣式。個別廣告可能會在一定程度上自定義樣式,我們稍後會看到。
.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 程式碼。讓我們從全域性變數開始
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-
用於在文件不可見時(例如,如果使用者已切換到另一個頁面)臨時儲存可見廣告列表。
設定
為了進行設定,我們在頁面載入時執行以下程式碼
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 來檢測這些選項卡切換並在期間停用我們的計時器。
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() 如下所示
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() 的呼叫。它的主要工作是每秒更新計時器並安排重繪以更新我們將在每個廣告中繪製的計時器。
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 物件作為輸入。程式碼如下
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() 函式來處理
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() 函式由 啟動程式碼 呼叫,用於選擇要呈現的文章和廣告並將其插入文件中
function buildContents() {
for (let i = 0; i < 5; i++) {
contentBox.appendChild(createArticle(loremIpsum));
if (!(i % 2)) {
loadRandomAd();
}
}
}
buildContents() 建立一個包含五篇文章的頁面。在每個奇數文章之後,“載入”一個廣告並將其插入頁面。文章使用一個名為 createArticle() 的方法建立後插入到內容框中(即包含所有網站內容的 <main> 元素),我們將在後面檢視該方法。
廣告是使用一個名為 loadRandomAd() 的函式建立的,該函式既建立廣告又將其插入頁面。我們稍後會看到這個相同的函式也可以替換現有廣告,但目前,我們正在將廣告附加到現有內容中。
建立文章
為了建立文章的 <article> 元素(以及其所有內容),我們使用 createArticle() 函式,該函式接受一個字串作為輸入,該字串是文章的完整文字,用於新增到頁面。
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,則該框被視為現有廣告元素;不是建立一個新的,而是更改現有元素以包含新廣告的樣式、內容和其他資料。這避免了在更新廣告時進行冗長的佈局工作的風險,這可能會在您首先刪除舊元素然後插入新元素時發生。
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.totalViewTime 和 adBox.dataset.lastViewStarted 設定為 0 來設定自定義資料屬性以跟蹤廣告的可見性資料了。
最後,我們設定將顯示計時器的 <div> 的 ID,該計時器將顯示廣告可見了多長時間,並將其類設定為 "timer"。初始文字設定為“0:00”,表示起始時間為 0 分 0 秒,並將其附加到廣告中。
如果我們沒有替換現有廣告,我們需要使用 Document.appendChild() 將元素附加到頁面的內容區域。如果我們要替換廣告,它已經存在,其內容已替換為新廣告的內容。然後我們對 Intersection Observer adObserver 呼叫 observe() 方法,開始監視廣告與視口交集的變化。從現在開始,每當廣告變得 100% 被遮擋或甚至一個畫素變得可見,或者廣告以某種方式透過 75% 可見時,都會執行觀察者的回撥。
替換現有廣告
我們的觀察者回調會關注那些被 100% 遮擋且總可見時間至少為一分鐘的廣告。當這種情況發生時,將呼叫 replaceAd() 函式,並以該廣告的元素作為輸入,以便可以用新廣告替換舊廣告。
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() 將用與新廣告對應的內容和資料替換現有廣告。
新廣告的元素物件將返回給呼叫者,以備需要。
結果
結果頁面如下所示。嘗試透過上下滾動進行實驗,並注意可見性的變化如何影響每個廣告中的計時器。還要注意,每個廣告在可見一分鐘後都會被替換(但廣告必須首先滾動出檢視並再次滾動回來),以及當文件被選項卡到後臺時計時器如何暫停。但是,用另一個視窗覆蓋瀏覽器不會暫停計時器。