
本指南將以專業且友善的語氣,深入說明如何在原生 HTML/JavaScript 環境下實作延遲載入機制。我們將依序介紹圖片、影片、iframe 以及第三方 JavaScript 腳本的延遲載入方法,每個部分都附有簡潔易懂的說明和可直接複製的程式碼範例。除了實作手法之外,我們也會討論延遲載入對 SEO(搜尋引擎最佳化)的影響,以及如何妥善處理 noscript 後備內容、內容可見性與爬蟲抓取等問題。最後的常見問與答(FAQ)單元將進一步澄清一些迷思並排解開發者常遇到的錯誤。現在,就讓我們一同探索延遲載入的世界吧!
圖片的延遲載入
圖片通常是網頁中數量最多且檔案體積最大的資源,因此也是延遲載入最典型的應用場景。透過延遲載入圖片,我們可以顯著減少首屏載入時間和流量用量。在開始實作之前,讓我們思考一下:有些圖片(例如首頁橫幅)一進頁面就可見,不應延遲載入;但還有許多圖片位在頁面下方,使用者不捲動就看不到,完全可以等需要時再載入。我們的目標就是在不影響使用者體驗的前提下,盡可能延後這些「非即時可見」圖片的載入時機。
針對圖片,有兩種主要的延遲載入方法:一是使用瀏覽器原生的 loading="lazy" 屬性,二是透過 IntersectionObserver API 在 JavaScript 中動態載入圖片。我們將分別介紹這兩種方法。
使用 loading="lazy" 屬性
最新的瀏覽器已經內建對圖片延遲載入的支援。我們可以在 <img> 標籤上添加 loading="lazy" 屬性,讓瀏覽器自行決定何時載入該圖片,而不必額外撰寫 JavaScript 程式碼或引入第三方函式庫。這種原生層級的延遲載入非常簡單,而且即使使用者停用了 JavaScript,此屬性對支持的瀏覽器依然有效。也就是說,瀏覽器會自動確保圖片正常載入——在支援 loading 屬性的情況下會延後載入,在不支援的情況下則會直接載入(確保相容性)。
以下是一個使用 loading="lazy" 的簡單範例:
<!-- 一般圖片元素,加入 loading="lazy" 屬性即可 -->
<img src="https://example.com/path/to/image.webp" alt="示意圖" loading="lazy" width="600" height="400">
上例中,我們有一個 600×400 的 JPEG 圖片。屬性 loading="lazy" 告訴瀏覽器:「如果這張圖像在初始載入時並沒有出現在視窗範圍內,就暫緩載入它,等到使用者即將看到它時再載入。」大多數現代瀏覽器(如 Chrome、Firefox,以及新版的 Safari 等)都已支持此屬性。注意:為了避免延遲載入造成版面跳動,建議在 HTML 中提供 width 和 height 屬性(或使用 CSS 的 aspect-ratio)來預留空間;如此一來,即便圖片尚未載入,網頁布局也能維持穩定,不會因圖片載入時尺寸變化而導致內容突然移位。
原生屬性最大的優點就是方便。然而,loading="lazy" 目前僅適用於 <img> 和 <iframe> 標籤,且對載入時機的掌控比較有限。如果我們需要更多自訂行為(例如在圖片實際進入視窗前一點就開始載入,或是希望在不支援此屬性的瀏覽器上提供替代方案),就需要借助 JavaScript 來實現。接下來,我們看看如何利用 IntersectionObserver API 進行圖片的延遲載入。
使用 IntersectionObserver 實作圖片延遲載入
IntersectionObserver API 提供了一種監視元素是否進入視窗(viewport)的方法,非常適合用來實作延遲載入。在 IntersectionObserver 出現之前,開發者常使用 scroll 事件配合 getBoundingClientRect() 來判斷圖片的位置,但那種方式相對低效且容易出錯。現在,我們可以讓瀏覽器幫我們監測:當指定元素進入可視範圍時,觸發回呼函式,在其中進行資源載入。
實作步驟概述如下:
- 初始設定資料屬性:
將圖片的實際 URL 放在自定義屬性(例如 data-src)中,而不要放在 src 屬性。src 則先放一個很小的佔位用圖檔(例如一個淡色小透明 GIF,或純灰色的佔位圖片),以避免 <img> 沒有 src 時瀏覽器出現破圖符號。也可以將 src 留空或指向一個本地的佔位圖片,只要能佔據適當空間即可。
- 建立 IntersectionObserver:
在 JavaScript 中創建一個新的 IntersectionObserver 實例,設定當觀察的目標元素出現在視窗時要執行的回呼函式。
- 觀察目標元素:
將所有需要延遲載入的圖片元素加入觀察名單。當使用者捲動頁面、某張圖片進入視口時,回呼函式將被觸發,我們便在其中把圖片的 data-src 內容賦值給真正的 src,然後取消對該元素的觀察(避免重複觸發)。
以下是實作圖片延遲載入的範例程式碼:
<!-- HTML:使用 data-src 儲存圖片真實路徑,src 指向一張輕量佔位圖 -->
<img data-src="https://example.com/path/to/high-res.webp"
src="/images/placeholder.webp"
alt="延遲載入範例圖片"
width="600"
height="400"
class="lazy-img">
// JavaScript:利用 IntersectionObserver 觀察並延遲載入圖片
document.addEventListener('DOMContentLoaded', function() {
const lazyImages = document.querySelectorAll('img.lazy-img[data-src]');
const config = {
root: null, // 根元素為視窗
rootMargin: '0px 0px 100px 0px', // 提前 100px 預載(讓圖片尚未完全進入視窗就開始載入)
threshold: 0.01 // 交叉比例閾值,只要露出 1% 就觸發
};
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
// 將 data-src 的值設定為真正的圖片 src
img.src = img.getAttribute('data-src');
img.removeAttribute('data-src'); // 移除 data-src,避免重複載入
observer.unobserve(img); // 取消觀察該圖片
}
});
}, config);
// 將每個延遲載入圖片元素加入觀察
lazyImages.forEach(img => {
imageObserver.observe(img);
});
});
上述程式碼做了以下幾件事:
- 在 DOMContentLoaded 時遍歷帶有 lazy-img 類別和 data-src 的 <img>,對每一個建立觀察。
- IntersectionObserver 的回調中,利用 entry.isIntersecting 判斷圖片是否進入視窗。如果是,則把真正的圖片 URL 從 data-src 挪到 src 上,隨即移除 data-src 並停止觀察這個元素。
- 我們使用了一點小技巧:rootMargin: '0px 0px 100px 0px'。這表示在元素距離視窗底部還有 100px 時就提前觸發載入。這樣當使用者快要看到圖片時,它已開始載入,確保滾動到該處時圖片已經顯示,避免出現空白再突然冒出的情況。
值得注意的是,如果要兼容不支援 IntersectionObserver 的老舊瀏覽器(例如 IE11),可以載入一個 IntersectionObserver 的 polyfill,或者退而求其次採用 scroll 事件監聽作為後備方案。另外,一些情況下我們可能希望在更早階段就載入特定圖片(例如關鍵的品牌圖片或首屏重要圖像),這時可以選擇不對那些圖片使用延遲載入,或者設定較大的 rootMargin 讓它們提早載入。
透過以上兩種方式,我們就能有效地對網頁中的圖片實現延遲載入:簡單情境下用 loading="lazy" 開箱即用,更進階的需求下採用 IntersectionObserver 獲取靈活控制。下一節,我們將把相同的理念應用到影片元素上。
影片的延遲載入
影片檔案通常更為龐大且載入耗時,但與圖片不同的是,用戶往往不會立刻在頁面載入時就播放所有影片。因此,對影片進行延遲載入能夠大幅減輕頁面初始載入負擔。同時,我們必須確保當使用者準備觀看影片時,影片能順利載入播放,不致產生明顯延遲。
HTML 提供了一些控制影片載入行為的屬性,再搭配 JavaScript 技巧可以達到延遲載入的目的。以下我們探討兩種策略:控制影片預載行為以及使用 IntersectionObserver 延遲載入影片。
控制影片預載行為(preload 屬性)
HTML5 <video> 標籤具有 preload 屬性,用於建議瀏覽器在頁面載入時如何預先抓取影片內容。preload 可取三個值:
- auto(預設):允許瀏覽器自行決定預載多少影片內容(通常會下載影片的部分資料,視瀏覽器實作而定)。
- metadata:僅預先載入影片的中繼資訊(例如影片長度、畫面尺寸等),不下載實際影像內容。
- none:不預先載入任何影片數據,除非使用者按下播放或以腳本方式手動啟動。
為了達到延遲載入影片的效果,我們通常將 preload 設為 none,讓影片在初始化時不下載資料流。以下是範例:
<video width="640" height="360" controls preload="none" poster="/images/video-placeholder.webp">
<source src="https://example.com/videos/sample-video.mp4" type="video/mp4">
您的瀏覽器不支援 HTML5 視訊播放。
</video>
在這段程式碼中:
- preload="none" 指示瀏覽器在未點擊播放前不要自動下載影片內容。換句話說,初始載入頁面時這支影片幾乎不會消耗流量。
- poster 屬性提供了一張預覽圖(通常是影片的縮圖或播放圖示),在影片真正播放前作為佔位顯示,增強使用者體驗。
- <source> 標籤內定義影片的來源 URL 和影片格式(type)。當使用者按下播放按鈕時,瀏覽器才會根據 source 指定的影片路徑開始抓取並播放影片。
我們也為 <video> 添加了 controls,以顯示播放控制介面(使用者可點擊播放)。若影片對使用者來說是可選擇播放的,那這樣的設置是合理的。
透過 preload="none",我們已經實現了被動式的延遲載入:除非使用者真的播放影片,否則瀏覽器不會下載影片數據。這非常適合那些非必要自動播放的影片,比如教學影片或用戶點擊才會播放的媒體內容。然而,有些情境下我們可能希望影片在快要進入使用者視野時就自動開始加載(甚至自動播放),以提供更順暢的體驗。這就需要借助 JavaScript 來更主動地控制影片的載入時機。
使用 IntersectionObserver 延遲載入影片
跟延遲載入圖片類似,我們可以使用 IntersectionObserver 來監控 <video> 元素何時進入視窗,並在適當的時機觸發影片載入。這裡我們提供兩種變化的方案:
- 方案一:延遲載入並自動預載 – 當影片進入視窗時,開始載入影片資料(相當於調用 video.load()),但不自動播放,等用戶按下播放時就能立即觀看,無需等待緩衝。
- 方案二:延遲載入並自動播放 – 當影片進入視窗時,若想自動播放(例如靜音的背景短片),則在載入的同時調用 video.play() 自動播放。但要留意瀏覽器的自動播放策略:通常只有靜音影片才能在未經使用者互動下自動播放。
以下是一個簡化的範例,示範如何在影片元素進入視口時動態設定影片來源並預載:
<video id="promoVideo" width="640" height="360" controls preload="none"
muted playsinline poster="/images/video-placeholder.webp">
<!-- 不設置 source,初始不載入影片 -->
尚未載入影片...
</video>
const videoElem = document.getElementById('promoVideo');
const videoSrc = "https://example.com/videos/promo.mp4";
// 建立 IntersectionObserver 觀察影片元素
const videoObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 當影片元素進入視窗,可在此設置影片來源並開始加載
videoElem.src = videoSrc;
videoElem.load(); // 開始載入影片資料
// (可選)若希望自動播放且影片靜音,則取消註解以下兩行
// videoElem.play();
// console.log("影片已自動播放");
observer.unobserve(videoElem); // 停止觀察,避免重複載入
}
});
}, { threshold: 0.5 }); // 當影片有一半區域可見時觸發載入
// 啟動觀察
videoObserver.observe(videoElem);
在這段程式中,我們的 <video> 起初沒有 <source> 或 src 指定(僅顯示一段「尚未載入影片…」的文字提示,可視為 noscript 狀況的簡單說明)。我們使用 JS 建立了一個 videoObserver 來監視這個影片元素:
- 一旦 promoVideo 有一半區域出現在視窗(設定 { threshold: 0.5 }),回呼函式就會把預先定義的影片 URL 賦給 videoElem.src,然後調用 videoElem.load() 讓瀏覽器開始載入影片數據。
- 如果目標是自動播放,比如背景宣傳影片或靜音自動播放影片,我們可以在載入後調用 videoElem.play()。上例中我們提供了這行代碼(默認註解),但只有在 muted 且 playsinline 的情況下,大部分瀏覽器才允許未經點擊的自動播放。
- 我們同樣在載入完畢後停止對 videoElem 的觀察,以防止重複操作。
透過這種方式,我們可以懶加載影片而不影響用戶體驗:當使用者即將瀏覽到影片時,我們確保影片已經在背後預先緩衝好。如果使用者點擊播放,影片可立即開始播放而無明顯延遲。如果影片是自動播放類型,使用 IntersectionObserver 也能實現在真正需要時才開始播放,避免在頁面外就浪費頻寬下載。
補充說明:對於 SEO 而言,影片本身的內容(畫面資訊)搜尋引擎並不能直接理解,因此延遲載入對 SEO 的直接影響不如對圖片或文字明顯。但我們仍要注意提供良好的後備資訊:例如在 <video> 標籤內提供文字描述(如上例中的「您的瀏覽器不支援 HTML5 視訊播放。」)以及 poster 縮圖。有需要時也可提供影片字幕或文字稿,讓搜尋引擎了解影片內容。這些細節都有助於無法播放影片的情況下仍提供足夠的資訊給使用者或爬蟲。
iframe 的延遲載入
<iframe> 常被用來嵌入第三方內容,例如 YouTube影片、Google地圖、社群貼文或廣告。這類嵌入框架通常會在載入時產生額外的網絡請求,甚至執行大量的腳本,對頁面速度影響較大。如果這些嵌入內容並非使用者立刻需要看到的,我們完全可以延遲它們的載入,以加快頁面初始呈現。
和圖片類似,iframe 也有原生的 loading="lazy" 支援,同時可以透過手寫 JavaScript 來更精細地控制載入時機。我們將先介紹使用 loading 屬性的簡便方式,接著說明如何用 IntersectionObserver 動態載入 iframe,甚至在使用者互動時再載入。
使用 loading="lazy" 屬性延遲載入 iframe
自從 Chrome 76+ 及部分現代瀏覽器開始,<iframe> 也支援了 loading 屬性。其作用與圖片相同:加入 loading="lazy" 的 iframe 會在接近可視區域時才載入內容,而非在頁面初始化時就立刻載入。這對於那些位於頁面底部的影片嵌入或地圖嵌入特別有用。
範例:
<!-- 延遲載入 YouTube 影片 iframe -->
<iframe src="https://www.youtube.com/embed/VIDEO_ID"
width="560"
height="315"
loading="lazy"
frameborder="0"
allowfullscreen
title="YouTube 影片播放"></iframe>
上述 <iframe> 指向一個 YouTube 影片嵌入,設定了固定的寬度和高度(避免布局跳動),加上了 loading="lazy"。如此一來,當使用者還沒捲動到影片區域時,瀏覽器不會去載入這個 iframe(也就不會載入 YouTube 的播放器程式碼),進而減輕頁面負擔。只有當使用者快要看到或已經看到該 iframe 時,瀏覽器才載入其內容。
需要注意的是,不同瀏覽器對 iframe Lazy loading 的支援情況曾有差異。例如,在早期 Safari 的某些版本中,iframe 的 lazy-loading 支援稍晚於圖片才加入。如果你擔心兼容性,可以在使用 loading="lazy" 的同時,也實作一套手動的延遲載入邏輯作為後援(或至少確保在不支援時 iframe 照常載入)。不過截至 2025 年,大多數主流瀏覽器(Chrome、Firefox、Edge,以及新版 Safari 等)都已普遍支援 iframe 的 lazy loading。
動態載入 iframe(IntersectionObserver 或使用者觸發)
有時我們希望對 iframe 的載入掌控更多。例如一些網頁開發者習慣在頁面初始時只放一個佔位元素或縮圖,等使用者點擊「載入地圖」按鈕或捲動到該區域時,才真正插入 <iframe> 元素。這種策略能徹底避免未使用的嵌入內容對頁面產生任何影響,直到用戶表現出明確的意圖。
讓我們以 Google 地圖嵌入為例,展示如何使用 IntersectionObserver 在元素進入視窗時,動態設定 iframe 的 src 以載入地圖:
<!-- 預先放一個空的 iframe 或占位容器 -->
<div id="mapContainer" style="width: 600px; height: 400px; background: #f0f0f0;">
<p>地圖載入中...</p>
</div>
const mapContainer = document.getElementById('mapContainer');
const mapIframeSrc = "https://www.google.com/maps/embed?pb=..."; // Google 地圖的嵌入網址
const iframeObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 建立 iframe 元素並設定屬性
const iframe = document.createElement('iframe');
iframe.width = "600";
iframe.height = "400";
iframe.src = mapIframeSrc;
iframe.style.border = "0";
iframe.allowFullscreen = true;
iframe.loading = "lazy"; // 即使動態插入,也可設定 lazy 以防萬一
iframe.referrerPolicy = "no-referrer-when-downgrade";
iframe.title = "Google 地圖";
// 將建立好的 iframe 放入容器,替換佔位內容
mapContainer.innerHTML = ""; // 清空容器內容
mapContainer.appendChild(iframe); // 插入真正的地圖 iframe
observer.unobserve(mapContainer); // 停止觀察
}
});
}, { rootMargin: "0px 0px 200px 0px" }); // 提前 200px 載入,確保平滑
iframeObserver.observe(mapContainer);
在這個例子中:
- HTML 部分我們沒有直接放入 <iframe>,而是放了一個固定大小的 <div> 容器作為地圖區塊的佔位,內含一行簡單的文字說明「地圖載入中...」。這確保了在地圖載入前,頁面上有對應的區塊占好位置。
- 使用 JavaScript 的 IntersectionObserver 觀察這個 #mapContainer。設定 rootMargin 為 200px 意味著當地圖容器距離視窗底部還有 200px 時就會開始載入,讓使用者滑動到那裡時幾乎同步看到地圖。
- 當觸發回呼時,我們動態創建一個 <iframe> 元素,設置必要屬性:src 指向地圖的嵌入 URL,width/height 設定大小(應與容器相同大小),allowFullscreen 等設置視需求增加。這裡我們甚至將 iframe.loading = "lazy" 一併設置,雖然iframe馬上要插入了,還是表明這是一個可延遲載入的內容(其作用有限,但以防萬一未來瀏覽器在這方面有額外優化)。
- 插入 iframe 前,先清空容器的內容(移除那行「地圖載入中...」文字),然後把新建的 iframe 加入 DOM。完成後取消對容器的觀察。
經過上述步驟,地圖嵌入只會在使用者即將瀏覽到地圖區域時才真正載入,達到了延遲載入 iframe 的目的。對於多個 iframe,需要類似地逐一觀察和處理。
另一種常見策略是使用者觸發:比如將 YouTube 影片縮圖顯示在頁面上,只有當使用者點擊播放按鈕時才插入真正的 YouTube <iframe>。這種方式雖不屬於「滾動延遲載入」,但也是 lazy loading 思維在用戶交互上的延伸 —— 按需載入。例如,許多網站在顯示嵌入影片前,先呈現一張封面圖和一個播放按鈕圖示;當使用者點擊時,再載入 YouTube 播放器。此舉避免了未播放影片時,就載入YouTube腳本和影片內容所造成的資源浪費。
無論透過瀏覽器內建屬性還是動態插入,iframe 延遲載入能為頁面性能帶來顯著幫助,特別是在有多個嵌入內容的情況下。然而,我們也要注意 SEO 和可及性方面的細節,這部分會在後續章節討論。
第三方腳本的延遲載入
第三方 JavaScript 腳本(例如分析工具、廣告腳本、社群插件、聊天小工具等)經常是網站隱藏的性能殺手。這些腳本由於是外部載入,響應時間往往不在我們掌控之中,而且在載入與執行過程中可能阻塞主執行緒或者消耗大量資源。延遲載入第三方腳本可以將它們對頁面初始渲染的影響降到最低,讓關鍵內容優先呈現。以下我們探討幾種延後第三方腳本載入的技巧:
使用 async 或 defer 屬性
在 HTML 中引入外部腳本時,基本的做法是使用 <script src="..."></script>。預設情況下,這會同步載入並執行腳本,期間會阻塞 HTML 解析,導致頁面渲染暫停(這就是為何大量同步腳本會嚴重拖慢頁面顯示的原因)。為了減輕這一問題,HTML 提供了兩個屬性:
- async: 當加上 async 時,瀏覽器會非同步下載該腳本,不會阻塞 HTML 解析。但一旦該腳本下載完成,會立刻中斷解析去執行它。也就是說,多個 async 腳本的執行順序不保證按照出現順序,誰先下載完就先執行。
- defer: 當加上 defer 時,瀏覽器同樣非同步下載腳本,且在 HTML 解析完成後才執行。並且多個 defer 腳本會按照在文件中出現的先後順序執行。defer 保證不阻塞 DOM 構建,也比較適合用於需要等待整個 DOM 就緒後再執行的腳本。
例如:
<!-- 非關鍵第三方腳本,使用 defer 不阻塞頁面 -->
<script src="https://example.com/some-third-party.js" defer></script>
使用 async/defer 並不能算真正意義上的「lazy loading」,但它推遲或並行了腳本載入與執行,避免阻塞渲染,是性能優化的重要一步。如果某個第三方腳本不是非得在首屏就立即執行(例如分析、廣告、聊天插件通常都不是剛進頁面就需要的),那麼在 <script> 標籤上加上 defer 幾乎是沒有壞處的。
然後,我們可以更激進一點:將第三方腳本的載入延遲到整個頁面載入完成之後,甚至延遲到用戶與頁面互動之後。這需要用到下一節介紹的動態腳本載入技巧。
動態注入腳本(按需載入第三方資源)
動態注入腳本是指通過 JavaScript 程式,在需要時才建立 <script> 節點並加到 DOM 中,以此來載入外部腳本。這給了我們完全的控制:可以選擇在頁面載入完成後的空閒時刻載入,或等待使用者觸發特定事件後再載入。例如,我們可能希望延遲 5 秒再載入一個分析腳本,避免干擾首屏渲染;或者當用戶捲到底部時才載入評論插件;又或者點擊按鈕時才載入線上客服聊天窗口的腳本。
讓我們先看一個簡單的動態載入腳本的通用函式:
/**
* 動態載入指定 URL 的腳本,載入完畢後執行 callback(若提供)
*/
function loadScript(url, callback) {
const script = document.createElement('script');
script.src = url;
script.async = true; // 使用 async 屬性,非必需但推薦
script.onload = () => {
console.log(`${url} 載入完成`);
if (typeof callback === 'function') {
callback();
}
};
script.onerror = () => {
console.error(`${url} 載入失敗!`);
};
document.body.appendChild(script);
}
這個 loadScript 函式會建立一個新的 <script> 元素,設定好 src,標記為 async(以防止它執行時阻塞),然後將其附加到文件的 <body> 或 <head>。當腳本載入完成時,我們透過 onload 事件呼叫可選的回呼函式,載入失敗則在 onerror 裡記錄錯誤。
有了這個通用函式,我們就能夠在任何需要的時機調用它,載入我們想要的第三方資源。例如:
- 延時載入:
利用 setTimeout 或 requestIdleCallback,在頁面閒置幾秒後再載入。setTimeout(() => loadScript('https://example.com/analytics.js'), 5000); 這樣在頁面載入5秒後才請求 analytics.js。
- 滾動觸發:
配合 IntersectionObserver,我們可以觀察頁面底部的某個標記,當用戶快捲到底時載入附加功能。如:監視一個隱藏在頁尾的 <span id="loadChat">,當它出現在視窗(表示使用者已經看完主要內容)時,動態載入聊天插件腳本:
const chatTrigger = document.getElementById('loadChat');
const observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
loadScript('https://example.com/live-chat-widget.js', () => {
initLiveChat(); // 假設腳本載入後需要呼叫初始化函式
});
observer.unobserve(chatTrigger);
}
});
observer.observe(chatTrigger);
這樣,只有當用戶真的瀏覽到頁尾(接近聊天功能的區域)時,才會觸發載入 chat widget 的腳本並初始化。若用戶從未滾到那裡,也就不浪費資源載入該腳本。
- 使用者互動:
例如有「顯示評論」按鈕,只有點擊之後才載入評論系統(如 Disqus)的腳本:
<button id="showComments">顯示評論</button>
<div id="commentSection"></div>
<script>
document.getElementById('showComments').addEventListener('click', () => {
loadScript('https://example.com/disqus-embed.js', () => {
// 假設 disqus-embed.js 載入後會自動生成評論內容到 commentSection
console.log("評論系統已載入");
});
});
</script>
在這個例子中,我們甚至將評論區塊隱藏起來,只有在使用者要求時才載入外部評論平台的程式碼,這有效避免了不看的評論卻佔用載入時間的問題。
透過動態注入的方式,幾乎任何第三方腳本都可以實現延遲載入,從而減輕對主要頁面流程的干擾。然而,在這裡也要注意幾點:
- 依賴性: 如果某第三方腳本是其他代碼的依賴,確保在載入順序上處理好。例如一些插件需要 jQuery,一定要確保 jQuery 載入後再載入插件腳本。對此,可以在 callback 中載入下一個腳本,或使用 Promise 來串連保證順序。
- 重複載入: 注意避免同一腳本重複載入。如果可能多處觸發載入同一資源,最好實作一個機制作檢查已載入清單。
- 資源提示(Resource Hint): 假如某第三方腳本體積很大但你確定稍後會載入,為了優化,你可以在 HTML <head> 中使用 <link rel="preconnect" href="https://example.com"> 或 <link rel="dns-prefetch" href="..."> 提前做 DNS 預查及連線,這樣真正載入時會更快。這不是延遲載入的部分,但跟其搭配能提升用戶體驗。
總而言之,延遲載入第三方腳本的方法靈活多樣:延後時機可以是時間、滾動、互動,實作方式通常是動態創建 script 元素。開發者可以根據實際需求組合運用,讓網站既保留第三方功能,又不至於開門見山地被它們拖慢。
延遲載入對 SEO 的影響與最佳實踐
在享受延遲載入帶來的性能紅利時,我們也必須審視其對 SEO(搜尋引擎最佳化)的影響。網站速度本身是SEO排名因素之一,因此 lazy loading 提升了加載速度,理論上有助於 SEO。然而,潛在的風險在於:如果延遲載入實作不當,重要內容可能無法被搜尋引擎爬蟲看到,從而影響索引。
搜尋引擎(例如 Google)的爬蟲在抓取頁面時,通常會先取得初始的 HTML。現今的 Googlebot 等還具備一定的 JavaScript 執行能力,也就是說它會在抓取後模擬一個瀏覽器執行頁面上的 JS,試圖取得動態渲染後的內容。然而,這種渲染有其限制:爬蟲不會像真人使用者那樣滾動畫面、點擊按鈕。因此,任何需要使用者操作才能出現的內容,爬蟲可能就抓取不到。了解這點後,我們來看具體建議:
- 重要內容不要依賴使用者觸發載入:
如果某段內容對SEO極其關鍵(例如文章正文、主要圖片),請勿把它設計成一定要滾動到或點擊才能顯示。如果真要使用 lazy loading,也應確保它在無使用者交互的情況下自動載入。一個例子是使用 IntersectionObserver 自動載入(這不需要使用者互動,只要元素進入視窗,就載入內容)。Google 官方建議延遲載入時不要完全依賴人工操作觸發,因為 Search 引擎在渲染頁面時不會模擬用戶捲動【具體請參考 Google 搜尋中心的說明】。換言之,如果你的 lazy-load 邏輯是「onScroll 時才加載內容」,那麼搜尋引擎可能永遠不會滾動頁面到底,自然也就看不到後面的內容。解法是使用 IntersectionObserver 或原生 lazy,使內容在進入 viewport(無需人工)時就自動載入。
- 提供 Noscript 後備內容:
noscript 標籤裡的內容會在使用者停用 JS 或爬蟲不執行 JS 時顯示出來。因此,對於用 JS 延遲載入的元素,我們應盡可能提供一份 noscript 版本。例如:
- 對延遲載入的圖片,在 <noscript> 裡放一個普通的 <img src="...">(相同的圖片URL和替代文字)。這樣即便JS不起作用,爬蟲和用戶也能看到圖片。搜尋引擎在索引圖片時,也會考慮 noscript 裡的 <img>。
- 對延遲載入的 iframe,可以在 noscript 中放一個鏈接或縮圖。例如 <noscript><a href="地圖連結網址">查看地圖</a></noscript> 或 <noscript><iframe src="..."></iframe></noscript>。前者提供至少一個可點擊的內容,後者則在無JS時依然嵌入內容(注意若雙重嵌入,同時有iframe和noscript iframe,瀏覽器有JS時只會執行前者,無JS時才執行noscript內的,所以不會重複)。
- 對重要的第三方內容,如果其腳本插入了一些文字資訊到頁面,建議在 HTML 中預先包含那些資訊(可以隱藏或標記為預設),或至少在 noscript 區塊提供提醒。例如一個評論區,如果 JS 延遲載入,那在 noscript 裡可放「請啟用 JavaScript 以載入評論」的字樣,避免無JS時頁面空空如也。
- 確保 lazy-loaded 資源可被抓取:
搜尋引擎對於延遲載入的圖片和影片,有專門的抓取與索引策略。如果圖片在初始 HTML 中根本不存在(例如以 JS 動態插入),那麼除非 Googlebot 執行你的 JS 把它載入了,不然就不知道有這張圖片。實務上,Googlebot執行JS渲染是一個第二階段(稱為 WRS,web rendering service),而這個渲染有可能排程較慢、並且不一定會模擬長頁面的滾動。爲了保險起見,你可以採取一些措施:
- 圖片站點地圖(Image Sitemap): 如果網站有大量以 lazy load 載入的圖片,考慮建立 image sitemap 把圖片 URL 都列出,供爬蟲參考。這樣即使圖片不是直接出現在初始HTML,搜尋引擎也能透過站點地圖知道它們的存在。
- 結構化資料:若圖片或影片對 SEO 很重要,可以使用 Schema.org 的結構化資料(例如 ImageObject 或 VideoObject)在頁面中描述它們。這也能在一定程度上告訴搜尋引擎有這些資源。
- Lazy Load 延遲不要過度:不要把所有內容都懶加載。如果整頁幾乎空無一物,全靠JS灌入,爬蟲可能會迷惑甚至放棄等待。確保你的頁面上始終有一定的實質內容直接由 HTML 載出,這對 SEO 和用戶的初始體驗都很重要。
- 避免延遲首屏關鍵內容:
這點不僅是 SEO,也是使用者體驗原則。首屏內容(用戶未捲動就能看到的部分)應盡量直接載入,不要lazy loading。否則使用者進入頁面時會看到空白或模糊的區塊,影響體驗,同時搜尋引擎也可能在截圖或摘要時抓不到任何實質內容。實際操作時,可以對首圖等above-the-fold元素不設 loading="lazy" 或在 IntersectionObserver 策略中給它們特殊處理(如馬上載入)。
- 測試與驗證:
完成延遲載入實作後,一定要透過工具檢查搜尋引擎能否看到內容。利用 Google Search Console 的「URL 檢查」功能,查看已渲染的HTML中是否含有你的延遲載入內容。例如,延遲載入的 <img> 在渲染後的 HTML 中是否有出現(通常Google渲染後能看到,前提是你的JS能在無人為操作下把它載入)。如果發現渲染後沒有出現,那就要調整策略,比如縮小 IntersectionObserver 的觸發範圍或改用原生 loading 等方法。務必做到關鍵內容對搜尋引擎完全可見。
綜上,延遲載入本身並非 SEO 大敵,只要採取適當的對策,一樣可以兼顧性能與可見性。記住這個原則:「該看見的,一定要讓它看見」—— 無論是對用戶還是對搜尋引擎。如果我們用 lazy loading 將該看見的東西藏了起來,那就喧賓奪主,違背初衷了。
常見問與答(FAQ)
透過本指南,相信你對各類外部內容的延遲載入實作都有了全面的瞭解。在實戰中,請靈活運用這些技巧,為你的網站提速的同時,兼顧 SEO 和用戶體驗。網頁性能優化是一門細緻的藝術,延遲載入只是其中一把利器。我們期待你將學到的知識應用到實際開發中,打造既快速又豐富的網路應用。Happy coding!