直接嵌入 iframe 對網頁效能有害
一般來說大家習慣在網頁上嵌入 YouTube 或是 third-party resource 像是 Twitter 的貼文,以至於我們寫範例常用的 CodeSandbox,大部分都是以 iframe 嵌入網頁的方式來使用,原因不外乎簡單好用又方便。但你是否曾經想過這些 iframe 事實上相當吃資源,且讓你的使用者數據變成數位廣告投遞的來源的風險。
embeded iframe 有多大 #
我把下列 embeded iframe 頁面單獨拉出來用 chrome 測出來的 network 流量與效能數據。
- Youtube https://www.youtube.com/embed/cowtgmZuai0
- Tweet https://platform.twitter.com/embed/Tweet.html?id=1488881026994778116
- CodeSandbox https://codesandbox.io/embed/pj127rwpvq
所得到的數據如下:
youtube-embed | tweet-embed | codesandbox-embed | |
---|---|---|---|
script runtime | 530 ms | 614 ms | 1602 ms |
js size | 762 KB | 401 KB | 1.9 MB |
但對於大部分的使用者來說,也許他們是用手機來到你的網站,並且只想快速瀏覽資訊,並不想看影片或是你嵌入的 iframe 有更深入的互動,你的 iframe 只是拖累你的網站速度,並不能讓大部分的使用者受益。
iframe 的 RWD 排版問題 #
雖然 youtube 有固定長寬比可以用 aspect-ratio
指定,由於 iframe 是另外一個網頁,如果是有文字內容的 iframe,高度在 RWD 時常常會因為文字隨著不同寬度的產生不同的換行而變化,我們一開始計算出 iframe 的長寬比就會有困難,無法指定長寬比就會使得 iframe 的載入會造成畫面的突跳,Web Vitals 的 CLS 分數就不會太好看。當然你也可以考慮固定 iframe 高度,overflow 就用 scrolling 的做法,但這在 mobile 垂直方向如果出現兩個 scrolling bar 容易會造成互相干擾。
embeded YoutTube iframe 解決方案 #
其實這個解決方案 web 一直都存在已久,就是 link。對於想要深入探索這個內容的人我們可以提供一個連結點擊,讓他可以到另外一個頁面去互動,而想停留在原本頁面的人看其他資訊的人,他們可以繼續往下走,而 iframe 其實就是一個我想停留在原頁面,同時也可以呈現其他頁面的解決方案,但其實我們可以透過 js 保留雙方的優點,我們一開始不直接嵌入 iframe,而是提供一個類似的元素可以讓使用者預覽 iframe 的內容,等到使用者想進一步了解在點擊的時候置換成真實的 iframe。這種靜態元素提供 iframe preview 但並無實際功能,Google 稱之為 Facade 。這樣的概念其實不需要太過複雜的前端框架,簡單的 js 跟 html 就可以實現。以 YouTube 為例來實現 facade 如下:
首先在 html 提供下面的 element,即可擁有影片縮圖預覽以及播放按鈕。
<div data-src="https://www.youtube.com/embed/cowtgmZuai0">
<img src="https://i.ytimg.com/vi/cowtgmZuai0/hqdefault.jpg" loading="lazy" />
<svg height="48" width="68" version="1.1" viewBox="0 0 68 48">
<path
d="M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z"
fill="#fe0001"
></path>
<path d="M 45,24 27,14 27,34" fill="#fff"></path>
</svg>
</div>
並在 element 綁定點擊事件,進行 iframe 的置換。
elm.addEventListener("click", function (event) {
var wrapper = event.currentTarget;
var src = wrapper.getAttribute("data-src");
if (src) {
wrapper.removeAttribute("data-src");
var iframe = document.createElement("iframe");
iframe.src = src + "?autopaly=1";
wrapper.innerHTML = "";
wrapper.appendChild(iframe);
}
});
如果你不想自行實作,也有 lite-youtube-embed Web Component 可以使用,各大前端框架也有出相關的 library。
但動態載入 iframe 還有其他的問題,在 iOS 下會無法使用自動播放功能需要點擊兩次的問題,因此可能要改使用 YouTube iframe api js library 來載入 YouTube iframe,來解決這個問題,但即便使用 library,我自己實測仍有釋出零星 case 無法自動播放。
考量到 mobile 上螢幕較小,若你的網頁並沒有需求希望使用者不要跳離,那我會建議在 mobile 上直接用 link 取代 iframe,這樣有以下的好處:
- 另開網頁就可以自動播放,影片大小比較不會像是 iframe 受到排版限制。
- 可藉由 Universal Link 跟 native YouTube App 整合,App 的 操作介面比 Web 更加的順暢。
- 如果使用者有無廣告的 Premium 會員,跳到 App 就可以直接享有本身無廣告的功能。
- 從 App 切換回原本網頁也可以有 Picture in Picture 的下方縮小播放模式。
因此用 anchor 取代原本的 div 如下:
<a data-src="https://www.youtube.com/embed/cowtgmZuai0">
<img src="https://i.ytimg.com/vi/cowtgmZuai0/hqdefault.jpg" loading="lazy" />
<svg height="48" width="68" version="1.1" viewBox="0 0 68 48">
<path
d="M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z"
fill="#fe0001"
></path>
<path d="M 45,24 27,14 27,34" fill="#fff"></path>
</svg>
</a>
並檢查螢幕寬度來判斷是否使用要使用 iframe 置換,或採取原生 link 跳轉:
elm.addEventListener("click", function (event) {
const isMobile = window.matchMedia("(max-width: 767px)").matches;
if (isMobile) {
return;
}
event.preventDefault();
var wrapper = event.currentTarget;
var src = wrapper.getAttribute("data-src");
if (src) {
wrapper.removeAttribute("data-src");
var iframe = document.createElement("iframe");
iframe.src = src + "?autopaly=1";
wrapper.innerHTML = "";
wrapper.appendChild(iframe);
}
});
Demo:

embeded Tweet iframe 解決方案 #
Twitter 本身其實有提供 API 給開發者取的推文資料,第一步你必須先去 https://developer.twitter.com/en/portal/dashboard 開 project 申請開發者 token。
npm i -s twitter-api-v2
就可以透過下面的方式取得推文資料,你就可以自己製作推文 html preview link,取代原本的 iframe
const { TwitterApi } = require("twitter-api-v2");
const twitterClient = new TwitterApi(TWITTER_DEV_TOKEN);
const roClient = twitterClient.readOnly;
const result = await roClient.v2.singleTweet(id, {
"media.fields": ["preview_image_url", "type", "url", "width", "height"],
"tweet.fields": ["attachments", "public_metrics", "created_at"],
"user.fields": ["id", "url", "profile_image_url", "description"],
expansions: [
"referenced_tweets.id",
"attachments.media_keys",
"in_reply_to_user_id",
"author_id",
],
});
Demo:
效能比較 #
我把本頁面取代 iframe 的 與原 iframe 頁面中的 script runtime 與 js size 做了個比較如下表:
facade | youtube-embed | tweet-embed | codesandbox-embed | |
---|---|---|---|---|
script runtime | 4 ms | 530 ms | 614 ms | 1602 ms |
js size | 2 KB | 762 KB | 401 KB | 1.9 MB |
對於不想要看影片或是使用 iframe 的使用者來說,效能跟體積上 facade 靜態 element 都 iframe 快了百倍有餘。
結語 #
直接嵌入 iframe 對網頁初始化效能有害,透過提供預覽內容的 facade element 能夠有效減少網頁初始化時 iframe 外部加載成本,而且由於是同頁面的 element,即可提供瀏覽器更穩定的排版計算避免 RWD 時 iframe 長寬比難以計算的問題。如果你被 iOS 動態載入 YouTube iframe 無法自動播放困擾,也可以參考我文中提供的解決方法。