[web] 內容 Image 預先或延遲載入(preload, lazy load, prefetch)
keywords: preload
, prefetch
, render-blocking
, lazy-load
🔖 The Complete Guide to Lazy Loading Images @ CSS Tricks 🔖 Lazy Loading Images and Video @ Google Developers > Web Fundamentals > Performance
當請求一個資源的時候,如果該資源存取完畢前無法觸發 window.onload
的事件,就稱作是 render-blocking
。
Preload
使用 rel="preload"
透過在 <link>
中使用 rel="preload"
可以在此頁(current page)預先載入(preload)稍後需要使用的資源,讓它們在稍後瀏覽器進行頁面轉譯的時候可以立即被使用,預先載入特別適合用在載入 CSS 字體檔、圖片或是較大的影音檔案。
要注意的是,透過 preload
只會提前去請求該資源,然後存在記憶體中,在你需要的時候自己再去使用(只限同一頁面)。
<head>
<meta charset="utf-8" />
<title>JS and CSS preload example</title>
<!--★★★★★ -->
<link rel="preload" href="style.css" as="style" />
<!-- stylesheet -->
<link rel="preload" href="main.js" as="script" />
<!-- script -->
<link rel="preload" href="main.js" as="image" />
<!-- image -->
<!-- MIME Type -->
<link rel="preload" href="intel-short.mp4" as="video" type="video/mp4" />
<!-- mp4 -->
<link rel="preload" href="fonts/foo.woff" as="font" type="font/woff" crossorigin="anonymous" />
<!-- woff font -->
<link
rel="preload"
href="fonts/roboto-webfont.ttf"
as="font"
type="font/ttf"
crossorigin="anonymous"
/>
<!-- ttf font -->
<link
rel="preload"
href="fonts/roboto-webfont.svg"
as="font"
type="image/svg+xml"
crossorigin="anonymous"
/>
<!-- svg font -->
<!-- media query preload -->
<link rel="preload" href="bg-image-narrow.png" as="image" media="(max-width: 600px)" />
<link rel="preload" href="bg-image-wide.png" as="image" media="(min-width: 601px)" />
<!-- preload 只是做預先下載的動作,還是自己使用它才有效 -->
<link rel="stylesheet" href="style.css" />
</head>
❗ preload 只是做預先下載的動作,還是要在需要的地方使用它。
預先載入 script 後使用
// <link rel="preload" href="my-script.js" as="script">
var preloadLink = document.createElement('link');
preloadLink.href = 'my-script.js';
preloadLink.rel = 'preload';
preloadLink.as = 'script';
document.head.appendChild(preloadLink);
在需要用到時再載入
// <script src="my-script.js"></script>
var preloadedScript = document.createElement('script');
preloadedScript.src = 'my-script.js';
document.body.appendChild(preloadedScript);
實測結果
測試程式碼:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
<link rel="stylesheet" href="style.css" />
<link
rel="preload"
href="https://c2.staticflickr.com/8/7151/6760135001_14c59a1490_o.jpg"
as="image"
/>
</head>
<body>
<h1>Hello</h1>
<img
style="max-width: 100%; height: auto;"
src="https://c2.staticflickr.com/8/7151/6760135001_14c59a1490_o.jpg"
/>
<script src="app.js"></script>
</body>
</html>
實際測試後,Chrome 在有下 preload
的情況下,不論內文有沒有用到該資源,都會先去把該資源載下來:
- 上圖是沒有用 preload,會按照各資源在 HTML 中的順序載入,所以先載
*.css
才載.jpg
- 下圖是有用 preload,會先把要 preload 的資源(
*.jpg
)載入後,才開始下載*.css
使用 rel="prefetch"
和 rel="preload"
會預先載入此頁要用的資源不同,rel="prefetch"
主要是用在下一頁會使用到該資源時預先載入;此外,瀏覽器會給予 preload
較高的優先順序(因為這頁就要用到)。
<!-- 實際測試的結果 prefetch 似乎沒有效果,仍然只能在此頁被使用 -->
<link rel="prefetch" href="/images/big.jpeg" />
Link prefetching FAQ @ MDN
字體相關(Font)
Developing a robust font loading strategy for CSS-Tricks @ zachleat
Google Font Preload
找到想要的字體
先找到想要的字體,以 Lato 來說,會取得一段 <link href="..." />
的連結:
點進去 href
中的網址後,會看到裡面是透過 CSS 定義的 font-face:
這裡面會有實際字體的網址,真正要 preload
是這個字體的路徑。
在網頁中使用
- 透過
<link rel="preload" ... />
把要使用的字體載入,在href
中要帶入的是在@font-face
中定義的實際字體網址路徑 - 當該字體載入後,就會直接套用網頁中
<head>
<!-- 使用 preconnect 可以提早與 google font 建立連線 -->
<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin />
<!-- 使用 preload 可以提早請求該資源 -->
<link
rel="preload"
href="https://fonts.gstatic.com/s/lato/v16/S6uyw4BMUTPHjx4wXiWtFCc.woff2"
as="font"
crossorigin
/>
<style>
/* 實際在 style 中使用 preload 回來的字體 */
@import url('https://fonts.googleapis.com/css?family=Lato&display=swap');
</style>
</head>
在這裡我們測試 latin
有用 preload(...Fcc.woff2
),latin-ext
沒有使用 preload(...Q7A.woff2
)。可以看到 latin
比 latin-ext
更提前載入一些:
範例程式碼
Google Font with Preload @ CodePen
其中 Lato 有使用 preload
而 Roboto 沒有,從下圖的 Network 中(使用 Slow 3G)可以看到 Lato 會比 Roboto 更早請求與取得:
檢視實際載入的字體
從 Computed 的最下方可以看到當前該元素實際套用到的字體為何:
Font Preload(Archive)
先使用 rel="preload"
:
<head>
<link rel="preload" href="//fonts.googleapis.com/earlyaccess/notosanstc.css" as="style" />
<link
rel="preload"
href="http://fonts.gstatic.com/ea/notosanstc/v1/NotoSansTC-Regular.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
/>
</head>
在 CSS 中要記得載入該字體,但此時先不使用該字體:
@import url(//fonts.googleapis.com/earlyaccess/notosanstc.css);
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
}
等字體載入完後再用 JS 的方式(document.fonts.load
)把它代入 style 中:
// 假設這裡要載入的字體是 Noto Sans TC
document.fonts.load('12px Noto Sans TC').then(function () {
const body = document.querySelector('body');
body.style.fontFamily = `"Noto Sans TC", ${window
.getComputedStyle(body)
.getPropertyValue('font-family')}`;
});
圖片相關
Image Preload with FadeIn Effect
這種方法的原理是一開始就利用 <img src="">
去 request 兩張圖片,小張的圖檔會疊在大張的圖檔上方,當大張圖檔的載完之後,把小張圖的 opacity
設成 0。
這種方法的優點時 src
不會突然被抽換掉,可以做出淡入的效果:
See the Pen Progressive load with img element (Preload) by PJCHEN (@PJCHENder) on CodePen.
Image Preload with image src tag
第二種方法的原理則是透過 new Image()
去請求圖片
預載圖片(image preload)
let img = new Image(200, 100);
img.src = 'https://foo.bar';
檢查圖片是否加載完成
// NOTICE: onload 要寫在 src 前,才能確保不會已經 load 完後才去註冊 load 事件。
var img = new Image();
img.onload = function (e) {
console.log('image preloaded');
};
image.src = 'url';
圖片錯誤處理(自動修復破圖)
// NOTICE: onerror 要寫在 src 前,才能確保不會已經 error 後才去註冊 error 事件。
img.onerror = function (e) {
let hasImgBrokenClass = this.classList.contains('img-broken');
if (!hasImgBrokenClass) {
this.src = 'img/oops.png';
this.classList.add('img-broken');
}
};
使用 background-image(Background Image Preload)
使用 CSS 中的 background-image 來載入圖片的話,理論上:
- 直到 CSSOM 被建構完成前,瀏覽器不會觸發下載影像(因此下載會產生阻塞)
- 如果在 CSS 中該影像被設定為
display:none;
則該影像不會下載。
使用 img 標籤
使用 <img src="foobar.png">
標籤來載入圖片的話,理論上:
- 瀏覽器會處理到
<img/>
標籤時直接下載圖片,而不會等到 CSSOM 建構完成,因此不會產生阻塞的情況。 - 也因此使用
<img/>
下載的圖片,即時使用display:none;
也還是會把該圖片下載下來。
2018-06-25
:實做時 Firefox 有些不同,可參考 Image Inconsistencies: How and When Browsers Download Images @ css wizardry
範例一
See the Pen Progressive background-image Loading by PJCHEN (@PJCHENder) on CodePen.
範例二
See the Pen Background Image Progressive Load (Lazy Loading) by PJCHEN (@PJCHENder) on CodePen.
Lazy Loading
和 Preload 不同,Lazy Loading 則是用來將不重要的資源延後載入的技術,待重要的資源處理完、或有需要的時候才去取用。這樣的技術很常使用在圖片的載入上,先載入一張檔案與解析度較差的圖片,待有需要的時候才去取得原圖。
🔖 Lazy Loading Images and Video @ Google Developers > Web Fundamentals > Performance
圖片相關 Image Related
使用 Intersection Observer API
See the Pen Lazy Load Image with Intersection Observer API by PJCHEN (@PJCHENder) on CodePen.
參考
- 🔖 Lazy Loading Images and Video @ Google Developers > Web Fundamentals > Performance
- HTML Link Types @ MDN
- Link prefetching FAQ @ MDN
- Using Preload and Prefetch in Your HTML to Load Assets @ Alligator
- Image Inconsistencies: How and When Browsers Download Images @ css wizardry