iT邦幫忙

2021 iThome 鐵人賽

DAY 6
3
Modern Web

今晚,我想來點 Web 前端效能優化大補帖!系列 第 6

Day06 X 圖片最佳化

給你五秒鐘思考一下,你在日常生活中還有在使用沒有任何圖片,包括小小 的 Icon 也沒有的網站嗎?我想大多數人的答案都是否定的。現今的網頁應用免不了會需要載入大量的圖片來加強網頁的視覺與提供方便使用者吸收的資訊,圖片因此成為網站中佔比最多的資源,讓我們換位思考,也就是說如果能優化這些圖片資源,也許會讓網站效能帶來很大的提升。


https://httparchive.org/ 這個網站定期針對前一百萬流量最多的網站進行 benchmark,發現圖片往往是平均起來佔比最大的資源。

Images are heavy.

圖片有不同格式,你得先了解它們適合的使用時機

不同的圖檔類型(如 .png .svg .jpg)有各自適合使用的時機,學會將不同類型圖片應用在適合的地方不僅可以提升使用者體驗或 UI 品質,某些狀況下也可以控制載入資源的大小進而提升效能。

先思考一下真的需要使用圖片嗎?

如果你的視覺需求用簡單的 CSS 就可以達成,就千萬不要多此一舉去使用圖片囉!

JPEG & PNG

我們知道 JPG 與 PNG 是由像素陣列(Pixel Array)來表示的圖像,又被稱為「點陣圖」,這兩種格式我想也是目前網頁中最常被使用的兩種格式。不過它們的檔案大小比較大,代表載入需要消耗比較多的時間與瀏覽器效能,所以我們得了解它們的使用時機,以及什麼時候可以尋找替換的格式。

在這之前我們得先聊聊圖片壓縮,透過圖片壓縮,可以大幅減少圖片的大小,以此來加快網站的載入速度。

可以看到雖然每張圖片的壓縮效果有差異,但至少都可以減少一定大小,這對於網站效能來說有相當大的幫助,因此現在開發網站時圖片基本上都一定會經過壓縮,有的企業會選擇建立自己的圖片壓縮服務,或是使用 webpack 等 module bundler 來將壓縮整合到開發流程裡,不過我們也可以使用一些第三方的壓縮服務,例如:

建議讀者可以自己玩玩看喔!

壓縮圖片是銀彈嗎?

雖說壓縮圖片真的可以顯著降低圖片的大小,但真的沒有任何 trade off 嗎?其實壓縮又分為兩種情況:

  • 有損壓縮:如 JPG,使用只取部分像素資料的方式來壓縮圖片大小,並且壓縮後是不可逆的。
  • 無損壓縮:如 PNG,壓縮後不影響圖片品質。

所以是否在意圖片品質是壓縮前要思考的事喔!(不過其實單純用肉眼看,畫質看起來並不會差太多)

回過頭來討論不同圖片格式適合使用的時機,通常相同尺寸且都經過壓縮後的 png 圖檔大小會比 jpg 還要大,原因在於 jpg 是採用失真壓縮演算法,因此壓縮率比 png 還要大。所以以我自己過往的經驗來說,如果是非常大的背景圖片,例如以下網站的背景圖

我會選擇使用 jpg,不僅檔案小了許多,實際上的視覺品質也不會差異太大。這樣看起來 png 是不是挺沒用的?當然不是。png 在處理含有文字、線條和輪廓較為明顯的影像表現較 jpg 來的好,當影像有明顯輪廓邊緣,jpg 的失真演算法在處理這種邊緣上就會產生瑕疵,png 的品質則會因為採用無失真演算法而較好。

另外如果你的圖檔未來是會一直有更改需求的,建議使用 png,原因是 jpg 在每次修改儲存後可能會導致圖片品質越來越差。

SVG

SVG 就是我們熟知的向量圖

SVG 的優點有

  • 無論放大多少都不會變模糊
  • 體積也比點陣圖還要小。
  • SVG 可以透過 Gzip 壓縮,PNG 與 JPG 透過 Gzip 壓縮檔案大小可能不會變小,甚至還可能更大。原因在於 PNG 與 JPG 本身就已經是經過壓縮的格式了。

如果是適合的使用情境,會建議能夠使用 SVG 就優先使用,SVG 適合的情境如企業的 Icon、圖表、圖示...等。而 PNG 與 JPG 等點陣圖其實也可以轉為向量圖,但並不是所有的點陣圖都適合轉檔,比較適合的情境如

  • 主要由幾何圖形組成的圖片
  • 較小的圖片 (長x寬)
  • 不希望產生圖片失真、邊緣會有點模糊的狀況

與 PNG 與 JPG 一樣,SVG 圖檔也可以進行最小化優化,例如可以透過 SVG 優化工具如 svgomg 來降低圖檔大小。

其實 SVG 的使用方式還蠻多元的,真的要說的話可能得分個幾篇文章來講解,不過繼既然系列文的主題是效能優化,就不多花篇幅在這上面了,有興趣的讀者可以參考以下幾個關鍵字:

  • SVG with CSS
  • Inline SVG
  • SVG With Base64

WebP

WebP 是 Google 於 2010 年推出的圖像格式,最大的優勢就是它的檔案大小比 PNG 與 JPG 小很多,

讓我們直接看看對比,YouTube 提供了 thumbnail image 的 URL,只要帶進影片 id 就能拿到影片的縮圖照片,也可以透過參數獲得不同格式與解析度的圖片。

https://i.ytimg.com/vi_webp/ZPBWje2kJ_U/maxresdefault.webp
https://i.ytimg.com/vi/ZPBWje2kJ_U/maxresdefault.jpg

在相同解析度的情況下,WebP 格式的大小為 195 KB,jpg 則為 285 KB,當然,這些都是已經經過壓縮的圖片,可以發現 WebP 真的是小了不少。

WebP 另外一個優勢就是支援動圖,也就是說常見的 GIF 也可以轉為 WebP 並且獲得更小的體積。

所以說 WebP 基本上是現代網頁的第一選擇,瀏覽器支援度的部分也只剩下 IE 沒有支援與 Safari 只有部分支援,而 IE 將在 2022 年終止服務了,基本上不用理會它(我好壞),所以可以說大多數主流瀏覽器都已經支援 WebP 囉!

最後附上 PNG、JPG、WebP 三種圖片格式的比較圖

Responsive Image

近年來裝置的數量與種類變得十分多元,手機、平板、筆電...等,會隨著廠牌與產品種類有不同的螢幕大小,如果開發者只提供一張照片,就想在不同的裝置都能夠完美顯示,是有點困難的。首先圖片可能會因此長寬比例失衡(除非把寬高寫死了)或是解析度失真,再來如果在小尺寸的裝置下載非常大尺寸的照片,不僅增加 page load time,瀏覽器也會為了縮小大圖而耗費性能。

這也是 LightHouse 衡量 performance 的一個重要 metric,詳細內容可以參考這裡

所以說,比較好的方法應該是針對不同的瀏覽器視窗大小、裝置解析度,呈現對應大小的圖片,這就是 Responsive Image 響應式圖片。

行前知識點:

  • Viewport: 視窗大小,指網頁的可視區域。
  • DPR (Device Pixel Ratio): 螢幕像素與 CSS pixel 之間轉換的倍率值(這個概念比較抽象,可以參考這篇文章)。

在說明 Responsive Image 的用法之前,先分享一個自己曾經的疑惑。

為什麼設計師出給我的圖,同一張會有分 1x, 2x, 3x 三個版本啊?不能只用其中一個就好嗎?

這裡的 1x, 2x, 3x 代表的其實是圖片的解析度(其實也跟圖片本身的大小有關),假設大多數裝置的 DPR 為 1,不過像是 iphone 的某些型號 DPR 可能會是 2 或是 3,這時如果呈現的還是適合 DPR 1 的圖片的話,就很有可能會產生模糊的狀況,因此才需要出不同尺寸的圖,依照裝置的 DPR 來呈現尺寸適合的圖片。(在這裡誠摯地跟之前跟我合作的設計師道歉,年少不懂事,出了 1x, 2x, 3x 給我,結果我沒搞清楚原因就只用了 3x ?)

Responsive Image Level 1

大家應該都知道在 HTML 中,圖片的 element 基本用法是

<img src="ironman.jpg" />

其實我們還可以透過 srcset 這個屬性,給予面對不同 DPR 時要顯示的圖片

<img 
  src="ironman.jpg"
  srcset="ironman_1x.jpg 1x, ironman_2x.jpg 2x" />

在 DPR 為 1 的情況下,瀏覽器會顯示 ironman_1x.jpg 這張圖,而在 DPR 為 2 的情況下,瀏覽器則會顯示 ironman_2x.jpg 這張圖,原來的 src 則變為 fallback 的選項,如果當前瀏覽器不支援 srcset 屬性或是找不到對應的 DPR,就會 fallback 成 src 的圖片。

Responsive Image Level 2

也許今天設計師出給你的圖並不是完全遵照 DPR 比率,不能用 1x, 2x 3x 來區別,只給了你 small, mid, large 三種版本的圖片,在這種狀況下,我們會希望可以由瀏覽器自己決定當前狀況下應該要顯示哪一張圖片,Img Element 也能夠做到這件事

<img 
  src="ironman_small.jpg"
  srcset="ironman_small.jpg 300w, ironman_mid.jpg 600w, ironman_large.jpg 1000w"
/>

眼尖的讀者應該發現圖片後的 1x, 2x 被換成 w 了,使用 w 後瀏覽器會根據裝置的 DPR 與 Viewport 來決定圖片的寬度。

例如說在 DPR 為 1 且螢幕寬度為 450px 的裝置下,瀏覽器選擇的圖片尺寸會是 1*450px = 450px,因為超過 ironman_small 的 300,卻在 ironman_mid 的 600 之內,瀏覽器會選擇使用 ironman_mid 這張圖片。如果是在 DPR 為 3 且螢幕寬度一樣為 450px 的裝置, 3*450 = 1350px,瀏覽器則會選擇 large 版本的圖片。

Responsive Image Level 3

如果使用了 step 2 提到的 w 的方式,就必須明確告訴瀏覽器圖片的大小。原因是平常我們可以透過 css 來指定 img element 的大小,但是瀏覽器會先解析 HTML 並開始載入圖片,經過這些步驟才會開始讀取 CSS 。前面提過,響應式圖片想讓瀏覽器自行決定要下載哪個尺寸的照片,但這個時候因為還沒讀取 CSS,所以瀏覽器根本不知道圖片的尺寸,那該怎麼辦呢?不如我們自己告訴它吧。

<!-- 新增了 sizes attribute -->

<img 
  src="ironman_small.jpg"
  sizes="(max-width: 400px) 80vw, 50vw"
  srcset="ironman_small.jpg 300w, ironman_mid.jpg 600w, ironman_large.jpg 1000w"
/>

這邊可能會有讀者誤會 size 屬性是去改變圖片的大小(當初的我也踩坑了),但是它其實只是告知瀏覽器選擇圖片尺寸的條件,如果都沒有指定的話,預設會是 100vw。

上面這段 HTML 的意思是:如果 viewport 小於或等於 400px,那圖片的寬度為 80vw (viewport 的 80%),其餘的狀況則為 50vw (viewport 的 50%)。假設在 DPR 為 1,螢幕寬度為 450px 的裝置下,所需的圖片尺寸為

450px * 0.5 (因為大於 400px,因此圖片寬度為 50vw)* 1 = 225px

因此瀏覽器會選擇下載 ironman_small.jpg 這張圖片。

Art direction

首先來看看 MDN Doc 對於 Art direction 給出的解釋與範例

The art direction problem involves wanting to change the image displayed to suit different image display sizes.

也就是希望能夠根據 viewport 指定不同尺寸的圖片,不然同一張圖從電腦版換到手機版如果等比例縮放的話應該會變很醜,或是圖片中的人事物會變得很小,這個問題我們可以利用 <picture> 這個 HTML element 來解決。

<picture> 是一個裡面可以裝有多個 <source> element 的 wrapper,瀏覽器會根據條件「由上而下」選出適合當前情況的 source。

<picture>
  <source
    media="(min-width: 800px)"
    srcset="ironman-large-1x.jpg 1x, ironman-large-2x.jpg 2x"
  />
  <source
    media="(min-width: 500px)"
    srcset="ironman-medium-1.jpg 1x, ironman-medium-2.jpg 2x"
  />
  <img src="ironman-small.jpg" alt="ironman" />
</picture>

在 viewport 大於 800px 的情況下,瀏覽器會根據 DPR 是 1 或是 2 選擇 ironman-large-1x.jpg 或是 ironman-large-2x.jpg,如果 viewport 介於 800px 與 500px 之間,則會根據 DPR 從 ironman-medium-1.jpg 與 ironman-medium-2.jpg 之間挑選,最後的 img element 與前面提到的一樣作為條件都沒有 match 時的 fallback option。

還能處理瀏覽器對圖片格式支援度問題

前面有說過 WebP 因為檔案大小的優勢已經成為圖片的首選格式,不過有些瀏覽器仍不支援,難道我得寫 JavaScript 判斷瀏覽器有沒有支援 WebP 再看要不要替換掉圖片嗎? 不用,picture 也強大到可以處理這件事了。

<picture>
  <source srcset="ironman.webp" type="image/webp" />
  <source srcset="ironman.jpg" type="image/jpg" />
  <img src="ironman.jpg" alt="ironman" />
</picture>

剛剛有說過 picture 中的 source 是「由上往下」判斷的,所以我們將最想要的 WebP 格式放在最上面,如果瀏覽器沒有支援 WebP,就會往下一個 source 尋找圖片,是不是很方便呢?

我們也可以將上面 media query 與 WebP 的範例結合在一起

<picture>
  <source 
      media="(min-width: 1920px)"
      srcset="ironman.webp" 
      type="image/webp" />
  <source 
      media="(min-width: 1920px)"
      srcset="ironman.jpeg" 
      type="image/jpeg" />
  <source
      media="(max-width: 500px)"
      srcset="ironman_small.webp" 
      type="image/webp" />
  <source
      media="(max-width: 500px)"
      srcset="ironman_small.jpg" 
      type="image/jpg" />
  <img src="ironman.jpg" alt="ironman" />
</picture>

我自己覺得 picture + source 真的非常強大,可以搭配許多 attribute 處理不同狀況,記得親自去體會看看喔!

<img srcset size>、<picture> 與 CSS Media Query 的差別

今天介紹的 <img srcset size>、<picture> 將圖片的選擇權交給了瀏覽器,讓瀏覽器在解析 HTML 時依當時狀況決定要下載什麼樣的圖片,而使用 Media Query 則是得先將全部圖檔先下載下來,再依照在 Media Query 裡設定的條件決定要顯示哪張圖片,因為必須先將圖片都下載下來,勢必會發出較多的網路請求,造成效能的耗損。

Image CDN

看到現在讀者應該會發現面對多元裝置的狀況,一個應用可能會需要出非常多尺寸的圖,但並不是所有人在開發時都有專業的設計時幫你做好不同尺寸的圖檔,這時可以考慮使用第三方的 Image CDN 服務 (關於 CDN 會在日後的章節詳細介紹),Image CDN 可以幫你即時轉換、resize 與優化圖片,並且存在 CDN 快取中,加速圖片傳遞的速度。

可以參考一些比較有名的 Image CDN 服務:

使用的方式大多是透過它們提供的 URL API,並帶入想要的圖片格式與圖片尺寸等條件來拿到圖檔,例如

https://res.cloudinary.com/fec-demo/image/fetch/w_200,h_200/{image path}.jpg'

有興趣的讀者可以參考這個教學影片

圖片優化 With Module Bundler

前面有提到我們可以利用 module bundler 來將圖片優化整合進開發流程裡,這邊就來簡單看看範例吧!因為筆者比較熟悉 Webpack,所以將會以 Webpack 為示範,不過相信其他 bundler 應該也有類似的工具,就交給各位自行研究囉。

Webpack 的許多功能都可以依靠 plugins 來達成,在 Webpack 中,可以透過 file-loader 或是 url-loader 來達成圖片打包,不過兩者相比,url-loader 多了 limit 的 config,只要圖片大小在 limit 之內,就會將資源轉成 DataURI (base64) 的格式並放到引入圖片的檔案裡。轉成 base64 的好處在於,網頁在渲染圖片時,不需要再透過額外的 request 抓取檔案,直接到檔案拿就好了,也就是說可以透過減少 Round Trip Time 來增加效能。不過這邊會限制 limit 當然就意味著不能都不管圖片大小就卯起來轉成 base64,原因是 base64 圖片內容會被 inline 到檔案中,最後打包時加在 JavaScript budle 中,如果不作限制,一不小心就可能造成 JavaScript 的 bundle size 過於肥大,反而拖垮了載入效能,因此必須好好衡量 limit 的上限喔!

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|gif|jpg)$/i,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8787, // 限制須轉為 base64 的文件大小 (單位:byte)
            },
          },
        ],
      },
    ],
  },
};

Base64 的優缺點

Base64 將圖片轉為編碼字串,讓開發者能將圖檔嵌入 HTML、CSS 或 JavaScript 程式碼,使用圖片時不需要再透過額外網路請求,以減少 Request。適合用在較少更新的小圖,例如 icon 等。使用方式為:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAYAAAA9zQYyAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzA..." />

要將圖片轉換成 base64 格式可以透過一些的 tool 例如 Base64 Image Encoder 來將圖片作轉換

Base64 的優點:

  • 也就是前面提過的可以減少 HTTP Request。

Base64 的缺點:

  • 圖檔平均來說比原本二進位檔案還大,這也是為什麼比較適合用在小圖。
  • 維護上比較困難。圖檔若有改動,整個編碼會不一樣,沒辦法像改動檔案或連結一樣容易,通常會搭配自動化工具與 plugins 如 postcss-base64 來維護。
  • 因為不是圖檔,所以沒辦法被瀏覽器快取。
  • 檔案會變大。Base64 編碼後會產生字串,若圖片太大,會導致編碼後字串非常長,增加檔案大小,因此還是建議使用 base64 編碼的圖不要太大,不過透過 Gzip 跟檔案的 Caching 可以緩解這個問題。

以我自己的經驗來說,如果是太大的圖,就不會使用 Base64 編碼的方式了,如果是 icon 或是縮圖則會考慮使用(還是要依照各個專案當前狀況決定)

舉一個實際的例子,其實 google 的圖片搜尋結果頁面的縮圖就是用 Base64 的形式喔

當點選圖片縮圖顯示圖片詳細資訊時有些大圖會被替換成 URL 的形式

想要更深入了解 Base64 的使用時機可以參考這篇文章

本日小結

今天介紹了不同圖片格式與它們各自適合使用的場景,也說明了我認為比較重要的圖片優化方式例如壓縮與響應式圖片。因為圖片在網頁資源上的佔比非常高,如果能夠盡量優化圖片,網頁載入效能將獲得顯著的成長。

本篇的最後,分享一個一張圖片也沒有卻很屌的網站,一起來看看世界上的第一個網頁吧!雖然真的一張圖片都沒有,但真的是偉大非常的第一步,看了真的好感動啊...
http://info.cern.ch/hypertext/WWW/TheProject.html

References & 圖片來源

https://cythilya.github.io/2018/08/24/responsive-images/
https://httparchive.org/
https://ithelp.ithome.com.tw/articles/10252501
https://cloudinary.com/
https://blog.gtwang.org/web-development/minimizing-http-request-using-data-uri/
https://www.shareus.com/windows/image-formats-comparison-webp-vs-jpg-vs-png.html
https://blog.infolink.com.tw/2021/rediscover-pixel-dpi-ppi-and-pixel-density/


上一篇
Day05 X Code Minimize & Uglify
下一篇
Day07 X Image Sprites
系列文
今晚,我想來點 Web 前端效能優化大補帖!30

1 則留言

0
alincode
iT邦新手 2 級 ‧ 2021-11-30 01:21:32

這邊可能會有讀者誤會 size 屬性是去改變圖片的大小 ---> sizes
/images/emoticon/emoticon19.gif

我要留言

立即登入留言