直接嵌入 iframe 對網頁效能有害 - Jason's Web Memo

直接嵌入 iframe 對網頁效能有害

一般來說大家習慣在網頁上嵌入 YouTube 或是 third-party resource 像是 Twitter 的貼文,以至於我們寫範例常用的 CodeSandbox,大部分都是以 iframe 嵌入網頁的方式來使用,原因不外乎簡單好用又方便。但你是否曾經想過這些 iframe 事實上相當吃資源,且讓你的使用者數據變成數位廣告投遞的來源的風險。

embeded iframe 有多大

我把下列 embeded iframe 頁面單獨拉出來用 chrome 測出來的 network 流量與效能數據。

所得到的數據如下:

youtube-embedtweet-embedcodesandbox-embed
script runtime530 ms614 ms1602 ms
js size762 KB401 KB1.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 在 web dev blog 稱之為 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,這樣有以下的好處:

  1. 另開網頁就可以自動播放,影片大小比較不會像是 iframe 受到排版限制。
  2. 可藉由 Universal Link 跟 native YouTube App 整合,App 的 操作介面比 Web 更加的順暢。
  3. 如果使用者有無廣告的 Premium 會員,跳到 App 就可以直接享有本身無廣告的功能。
  4. 從 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:

Ben Lesh
@BenLesh
Periodic reminder that tabs are definitively better than spaces for one reason alone: Accessibility. Users can change the display size of tabs to whatever suits their needs. This can be helpful for people with reading or vision difficulties.
02:24 PM, Feb 02, 2022
12 Retweets 4 Quote Tweets 85 Likes 11 Replys

效能比較

我把本頁面取代 iframe 的 與原 iframe 頁面中的 script runtime 與 js size 做了個比較如下表:

facadeyoutube-embedtweet-embedcodesandbox-embed
script runtime4 ms530 ms614 ms1602 ms
js size2 KB762 KB401 KB1.9 MB

對於不想要看影片或是使用 iframe 的使用者來說,效能跟體積上 facade 靜態 element 都 iframe 快了百倍有餘。

結語

直接嵌入 iframe 對網頁初始化效能有害,透過提供預覽內容的 facade element 能夠有效減少網頁初始化時 iframe 外部加載成本,而且由於是同頁面的 element,即可提供瀏覽器更穩定的排版計算避免 RWD 時 iframe 長寬比難以計算的問題。如果你被 iOS 動態載入 YouTube iframe 無法自動播放困擾,也可以參考我文中提供的解決方法。

Ref

Webmention 社群迴響 22

喜歡 18
轉推 1
引用或評論 3

用 Webmentions 參與社群迴響

透過 twitter 轉推,你的轉推評論之後會更新在上面社群迴響的引用評論列表。

轉推這篇文章

如果你的 blog 文章想要引用本文,歡迎透過下方表單用將你的 blog 文章網址傳送給我,若你的 blog 文章含有正確的本文網址連結,並且 blog 文章本身支持 microformat,之後你的引用資訊會更新在上面社群迴響的引用評論列表。

社群迴響將不定期更新,不保證同步,同時有資料缺漏的可能性。最新回應請參考以下連結:

Jason Chen - Yahoo Taiwan EC Web frontend engineer currently. Write something about web and React.js here

Jason Chen

Yahoo Taiwan Sr. Frontend Engineer. Write something about web and React.js here.

Other Posts