在滑 FB、IG 或各大新聞網站時,頁面永遠沒有盡頭——當你快滑到底部時,新內容就自動補充進來。這種「無窮滾動(Infinite Scroll)」能大幅提升留存率與瀏覽體驗。今天我們就來學習如何結合 DOM 動態渲染與滾動距離算式,實作這個經典功能。
forEach 結合 document.createElement() 動態產出對應的卡片 HTML 結構並插入頁面。window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 100 時觸發新資料載入;並設定 isLoading 布林變數防止連續滾動造成的重複請求與渲染衝突。1. 資料驅動畫面 (Data-Driven UI) 與 createElement 動態渲染
為什麼選用這個觀念?
在實際開發中,網頁的資料通常是從伺服器端傳回來的 JSON 陣列。我們不該在 HTML 寫死靜態標籤,而是透過 JavaScript 讀取資料陣列後,動態將資料「轉化」為 DOM 節點並掛載(appendChild)到頁面上。
這個機制在幹嘛?
將資料與畫面解耦:資料(Data)負責記錄內容,DOM 操作負責把資料呈現出來。
有沒有其他替代方案?
innerHTML 樣板字串(Template Literals)
<div class="card">${title}</div> 拼接後丟給 innerHTML。createElement 建立節點後綁定事件會更安全且不會干擾已有節點。2. 觸底偵測與防止重複載入 (Loading Lock)
為什麼需要這個公式與狀態鎖?
觸底算式為:視窗高度 (innerHeight) + 滾動距離 (scrollY) >= 網頁總高度 (scrollHeight) - 預留值。
當使用者滑動到接近底部時,scroll 事件會以非常高的頻率觸發。如果沒有使用「載入中標記 (Loading Lock)」,系統會瞬間重複觸發數十次載入請求,造成畫面塞爆或效能卡頓。
這個機制在幹嘛?
設定一個布林變數 isLoading = true,當正在載入新資料時拒絕再次觸發,直到資料渲染完成才解鎖(isLoading = false)。
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<title>第十一課 - 卡片動態渲染與無窮滾動</title>
<style>
body { font-family: sans-serif; padding: 20px; background-color: #f0f2f5; }
.container { max-width: 600px; margin: 0 auto; }
/* 卡片容器網格 */
.card-list { display: flex; flex-direction: column; gap: 15px; }
/* 單張卡片樣式 */
.card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
transition: transform 0.2s ease;
}
.card:hover { transform: translateY(-3px); }
.card h4 { margin: 0 0 10px 0; color: #333; }
.card p { margin: 0; color: #666; font-size: 14px; }
/* 載入中提示文字 */
.loading-text {
text-align: center;
padding: 20px;
color: #888;
font-weight: bold;
display: none; /* 預設隱藏 */
}
.loading-text.show { display: block; }
</style>
</head>
<body>
<div class="container">
<h2>動態探索卡片牆</h2>
<div id="cardList" class="card-list"></div>
<div id="loading" class="loading-text">⏳ 正在載入更多卡片...</div>
</div>
<script>
// ----------------------------------------------------
// JavaScript 邏輯 (資料驅動與無窮滾動)
// ----------------------------------------------------
const cardList = document.getElementById("cardList");
const loading = document.getElementById("loading");
// 全域狀態:記錄目前載入到第幾批資料,以及是否正在載入中 (防重複觸發)
let pageCount = 1;
let isLoading = false;
// 模擬後端 API 產生資料的函式
function fetchMockData(page) {
const items = [];
for (let i = 1; i <= 5; i++) {
const id = (page - 1) * 5 + i;
items.push({
title: `卡片標題 #${id}`,
content: `這是第 ${page} 頁產生的動態卡片內容,編號為 ${id}。DOM 動態生成的元件能彈性展示多變的資料格式!`
});
}
return items;
}
// 核心渲染函式:將資料陣列轉化為 DOM 節點並貼上頁面
function renderCards(data) {
data.forEach(function(item) {
// 1. 建立外層卡片 div
const card = document.createElement("div");
card.className = "card";
// 2. 建立標題 h4
const title = document.createElement("h4");
title.innerText = item.title;
// 3. 建立內文 p
const content = document.createElement("p");
content.innerText = item.content;
// 4. 組合 HTML 結構
card.appendChild(title);
card.appendChild(content);
// 5. 掛載到畫面的卡片清單中
cardList.appendChild(card);
});
}
// 模擬非同步載入新資料的流程
function loadMoreCards() {
if (isLoading) return; // 如果正在載入中,立刻擋掉,不重複執行
isLoading = true; // 上鎖
loading.classList.add("show"); // 顯示「載入中」提示
// 模擬網路延遲 0.8 秒後載入資料
setTimeout(function() {
const newData = fetchMockData(pageCount);
renderCards(newData);
pageCount++; // 頁數加 1
isLoading = false; // 解鎖
loading.classList.remove("show"); // 隱藏「載入中」提示
}, 800);
}
// 第一步:初始化頁面,預設先載入第一批卡片
loadMoreCards();
// 第二步:監聽視窗滾動事件,實作觸底自動載入
window.addEventListener("scroll", function() {
// 算出目前視窗底部距離整份文件底部的剩餘像素數
const scrollPosition = window.innerHeight + window.scrollY;
const threshold = document.documentElement.scrollHeight - 100; // 距離底部 100px 時觸發
// 條件成立且非載入狀態時,自動載入下一頁
if (scrollPosition >= threshold) {
loadMoreCards();
}
});
</script>
</body>
</html>
今天我們打通了「資料陣列 → DOM 卡片渲染」與「視窗距離算式 → 動態觸發載入」的完整流程。這是前端開發中蠻關鍵的思維轉換,代表我們大致已經具備處理動態資料串接與流暢 UX 控制的能力。
網站中經常需要在同一個區塊切換顯示不同頁籤內容(例如:商品詳情 / 規格說明 / 顧客評價),這要怎麼做得整齊呢?
【第十二課】頁籤切換:Tab 頁籤內容切換器(Data Attribute 自訂屬性與狀態控制),我們將學習如何運用 HTML5 的 data-* 屬性輕鬆打造多頁籤組件。