PJCHENder 未整理筆記

[Web] 內容 Image 預先或延遲載入(preload, lazy load, prefetch)

2018-06-25

[Web] 內容 Image 預先或延遲載入(preload, lazy load, prefetch)

@(Web Development)[html]

keywords: preload, prefetch, render-blocking, lazy-load

:bookmark: The Complete Guide to Lazy Loading Images @ CSS Tricks
:bookmark: 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 只會提前去請求該資源,然後存在記憶體中,在你需要的時候自己再去使用(只限同一頁面)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<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>

:exclamation: preload 只是做預先下載的動作,還是要在需要的地方使用它。

預先載入 script 後使用

1
2
3
4
5
6
// <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);

在需要用到時再載入

1
2
3
4
// <script src="my-script.js"></script>
var preloadedScript = document.createElement('script');
preloadedScript.src = 'my-script.js';
document.body.appendChild(preloadedScript);

實測結果

測試程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!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 較高的優先順序(因為這頁就要用到)。

1
2
<!-- 實際測試的結果 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="..." /> 的連結:

Imgur

點進去 href 中的網址後,會看到裡面是透過 CSS 定義的 font-face:

Imgur

這裡面會有實際字體的網址,真正要 preload 是這個字體的路徑。

在網頁中使用

  • 透過 <link rel="preload" ... /> 把要使用的字體載入,在 href 中要帶入的是在 @font-face 中定義的實際字體網址路徑
  • 當該字體載入後,就會直接套用網頁中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<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)。可以看到 latinlatin-ext 更提前載入一些:

Imgur

範例程式碼

Google Font with Preload @ CodePen

其中 Lato 有使用 preload 而 Roboto 沒有,從下圖的 Network 中(使用 Slow 3G)可以看到 Lato 會比 Roboto 更早請求與取得:

imgur

檢視實際載入的字體

從 Computed 的最下方可以看到當前該元素實際套用到的字體為何:

Imgur

Font Preload(Archive)

先使用 rel="preload"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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 中要記得載入該字體,但此時先不使用該字體

1
2
3
4
5
6
7
@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 中:

1
2
3
4
5
6
7
// 假設這裡要載入的字體是 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)

1
2
let img = new Image(200, 100);
img.src = 'https://foo.bar';

檢查圖片是否加載完成

1
2
3
4
5
6
7
8
// NOTICE: onload 要寫在 src 前,才能確保不會已經 load 完後才去註冊 load 事件。
var img = new Image();

img.onload = function (e) {
console.log('image preloaded');
};

image.src = 'url';

圖片錯誤處理(自動修復破圖)

1
2
3
4
5
6
7
8
9
// 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 來載入圖片的話,理論上:

  1. 直到 CSSOM 被建構完成前,瀏覽器不會觸發下載影像(因此下載會產生阻塞)
  2. 如果在 CSS 中該影像被設定為 display:none; 則該影像不會下載。

使用 img 標籤

使用 <img src="foobar.png"> 標籤來載入圖片的話,理論上:

  1. 瀏覽器會處理到 <img/> 標籤時直接下載圖片,而不會等到 CSSOM 建構完成,因此不會產生阻塞的情況。
  2. 也因此使用 <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

Lazy Loading Images Complete Guide @

使用 Intersection Observer API

See the Pen Lazy Load Image with Intersection Observer API by PJCHEN (@PJCHENder) on CodePen.

## 參考
Tags: html

掃描二維條碼,分享此文章