響應式影像與優化

by.Genesis發表於2022-02-18

影像是 Web 重要組成部分,將影像優化好了效能問題就解決了至少一半

要在 Web 網頁上放置影像,我們需要使用 <img> 元素。這是一個空元素,它需要指定一個 src 屬性。如果影像名為 avatar.jpg 並且它與您的 HTML 文件位於同一路徑中,則可以按如下方式引入:

<img alt="avatar" src="avatar.jpg">

為了確保影像的可訪問性,我們新增了 alt 屬性。該屬性的值應該是圖片的文字描述,當影像無法顯示或看不到時,作為影像的替代;例如,使用者通過螢幕閱讀器訪問您的頁面。

接下來,我們新增 widthheight 屬性來指定影像的寬度和高度,也就是影像的尺寸。

<img alt="avatar" height="400" src="avatar.jpg" width="400">

在影像上指定 widthheight 屬性,瀏覽器就知道要為這張影像保留多少空間。如果忘記指定影像的尺寸會導致佈局偏移,因為瀏覽器不確定影像需要多少空間,佈局偏移就會影響效能

但是作為一個響應式設計的網頁我們影像的父級元素的大小可能不足以容納一個固定大小的影像,

img-width-overflow

因此我們需要一個樣式來使影像的大小不會超出父級元素

img {
  max-width: 100%;
}

這時又會引出另一個問題,影像的寬度變得自適應了,而高度被我們新增的 height 屬性固定了,為了使影像高度也能自適應,我們需要通過樣式給影像的高度指定為自動:

img {
  max-width: 100%;
  height: auto;
}

到這裡我們的影像可以完美地適應父級元素的大小了,

img-width-160

img-width-240

不過我們這裡又可以發現另外一個問題,無論影像的容器有多大載入的始終都是同一張影像,很顯然,在一個小空間里載入顯示一張高解析度的影像是一種浪費,我們希望能夠為不同大小的影像容器提供不同解析度的影像

響應式影像

如果我們可以給一個影像容器提供多張影像讓瀏覽器自行選擇合適的那一張來載入顯示豈不是很美好?

<img> 元素新增了一個 srcset 屬性,這是一個集合屬性,通過該屬性我們可以給影像提供多個源路徑,使用逗號進行分隔

<!-- 提供單個影像源 -->
<img src="avatar-800w.jpg">
<!-- 提供多個影像源 -->
<img src="avatar.jpg"
     srcset="avatar-small.jpg 400w,
             avatar-large.jpg 800w">

同時,每個影像路徑後面可以新增一個寬度描述符,用空格分割,比如 400w 表示 avatar-small.jpg 影像的寬度是 400 畫素

寬度描述符的作用是告訴瀏覽器這張影像的寬度,讓瀏覽器在還沒有下載影像的情況下就能確認影像的寬度,注意寬度描述符的單位是 w

srcset 屬性一起出現在標準中的還有一個 sizes 屬性,這兩個屬性需要搭配使用才能讓瀏覽器知道自己該載入哪張影像

<img src="avatar.jpg"
     srcset="avatar-small.jpg 400w,
             avatar-large.jpg 800w"
     sizes="400px">

sizes 屬性用於告訴瀏覽器該影像可能的顯示寬度,比如這裡的值 400px 表示該影像可能的顯示寬度為 400 畫素,所以瀏覽器會載入 400 畫素寬的小影像,而忽略那張 800 畫素寬的大影像,畢竟影像解析度越大理論上來講檔案體積也就越大

sizes 的值必須是一些固定有效的長度,比如 100vw, 100px 等等,不能是百分比之類的,sizes 的作用只是告訴瀏覽器該影像可能的顯示寬度

另外 sizes 屬性還支援使用媒體查詢提供由逗號分隔的多個寬度值,為不同視口大小指定不同的影像可能寬度

<img src="avatar.jpg"
     srcset="avatar-small.jpg 400w,
             avatar-large.jpg 800w"
     sizes="(max-width: 600px) 400px, 800px">

sizes="(max-width: 600px) 400px, 800px" 表示視口寬度不足 600 畫素的時候,影像寬度為 400 畫素,此時瀏覽器依然是載入小的影像,而當視口寬度大於 600 畫素的時候,影像寬度為 800 畫素,瀏覽器就會載入大的影像

響應式影像除了可以適應視口的寬度,還可以適應不同的裝置畫素比,比如這裡,當視口寬度不足 600 畫素的時候,如果裝置畫素比為 1 則瀏覽器還是載入顯示小的那張影像,但是如果裝置畫素比為 2 的話,則瀏覽器會載入顯示大影像

總之,瀏覽器可以結合這幾個條件從 srcset 中提供的影像中選擇最合適的那張

如果瀏覽器不支援 srcsetsizes 屬性,將會忽略它們,並使用 src 提供的影像,目前主流瀏覽器都支援這兩個屬性,詳見 https://caniuse.com/srcset

傳統影像格式

JPEG 是使用廣泛的格式,這是一種有損格式,解碼速度快,適用於照片,但是它不支援透明,所以,我們同時會使用另一種影像格式

PNG 支援透明,不過它是一種無損格式,壓縮率不高,如果用於照片的話影像檔案的體積往往會很大

通常情況下這兩種格式我們會根據實際情況選擇使用,色彩豐富的照片就考慮使用 JPEG 格式,需要透明的時候就用 PNG 格式,這兩種影像格式都不支援動畫

GIF 支援動...,我們現在應該沒有理由還使用 GIF

這些傳統影像格式各自都有明顯的缺點,為了解決這些缺點,有了接下來這些新的影像格式

現代影像格式

WebP 影像格式由 Google 建立,同時支援無損和有失真壓縮以及透明,動畫,絕大多數情況下是比 JPEG, PNG 和 GIF 更優秀的影像格式,是設計出來取代他們的,創造至今已經得到了絕大多數瀏覽器支援(https://caniuse.com/webp

AVIF 支援無損和有失真壓縮,動畫,透明,是比 WebP, JPEG, PNG 和 GIF 更優秀的影像格式,是設計出來取代他們的,目前被部分主流瀏覽器的新版本支援(https://caniuse.com/avif),Safari 暫時不支援,但預計不久就會,畢竟 Apple 公司是建立這種格式的組織成員之一,現在我們也可以在專案中使用了,後面會有使用方式

雖然 AVIF 比 WebP 更優秀,但是 WebP 比 AVIF 支援的瀏覽器更多,所以目前來說這兩種格式我們都會使用到,現代影像格式的引用方式和傳統影像是一樣一樣的,通過 <img> 元素

<img src="avatar.avif">

不過對於不支援這種格式的瀏覽器來說就顯得無語了,好在有個新的 <picture> 元素來應對這種情況

使用 <picture> 元素可以為單個影像提供多種影像格式供瀏覽器選擇。一個 <picture> 元素必需包含一個 <img> 元素,

<picture>
  <img alt="avatar" src="avatar.jpg">
</picture>

只是這樣還沒有任何特別的作用,我們還可以在 picture 元素中使用 <source> 元素為影像提供不同格式的源

<picture>
  <source srcset="avatar.avif" type="image/avif">
  <img alt="avatar" src="avatar.jpg">
</picture>

這樣就比較有意思了,如果瀏覽器支援 AVIF 格式的影像就會載入顯示 avatar.avif 檔案,同時忽略該 picture 元素內其它格式,否則就會降級載入 avatar.jpg 檔案

如果瀏覽器連 <picture> 元素都不支援,將會簡單地忽略它,直接顯示 <img> 元素的內容,另外還可以提供多個 <source> 元素,瀏覽器會自上而下地選擇第一個它支援的格式

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

將需要優先顯示的放在最前面就可以了,type 屬性用於告訴瀏覽器該影像的格式,所有主流瀏覽器都支援 <picture> 元素 ,關於 <picture> 元素的瀏覽器支援可檢視 https://caniuse.com/picture

響應式現代影像格式

我們注意到 <source> 元素指定資源路徑的屬性是 srcset,也就是說,一個 <source> 元素也是可以同時指定多個影像路徑的,結合前面的內容整合到一起,一個影像元素可能就會如下所示

<picture>
  <source
    sizes="(max-width: 600px) 100vw, 50vw"
    srcset="avatar-small.avif 400w,
            avatar-large.avif 800w" type="image/avif">
  <source
    sizes="(max-width: 600px) 100vw, 50vw"
    srcset="avatar-small.webp 400w,
            avatar-large.webp 800w" type="image/webp">
  <img sizes="(max-width: 600px) 100vw, 50vw" src="avatar.jpg"
    srcset="avatar-small.jpg 400w,
            avatar-large.jpg 800w">
</picture>

影像壓縮

影像壓縮可以分為無損和有損兩種,無失真壓縮就是在不損失影像質量的情況下減小影像檔案的大小,如果無失真壓縮已經不能給我們帶來多少收益的時候,我們可以考慮有失真壓縮,適當降低一下影像的質量,可以有效地減小影像檔案的大小

我們將影像轉換為 WebP 或者 AVIF 的時候,應該從原始檔直接匯出或者通過無損的格式(如 PNG)來轉換,如果用有損的格式(如 JPEG)再來轉換為有損的 AVIF 的話,那影像質量就損失兩次了

延遲載入

只需要給 <img> 元素新增一個 loading="lazy" 屬性即可

<img loading="lazy" src="avatar.jpg">

搭配 <picture> 元素一起食用,

<picture>
  <source srcset="avator.avif" type="image/avif">
  <img loading="lazy" src="avator.jpg">
</picture>

不支援的瀏覽器會自動忽略該屬性,可以給不支援的瀏覽器提供外掛來實現該功能

<img alt="avator" class="lazyload" data-src="avator.jpg" loading="lazy">

<script>
;(function() {
  if ('loading' in HTMLImageElement.prototype) {
    var images = document.querySelectorAll('img.lazyload')
    images.forEach(function(img) {
      img.src = img.dataset.src
    })
  } else {
    var script = document.createElement('script')
    script.async = true
    script.src = '//afarkas.github.io/lazysizes/lazysizes.min.js'
    document.body.appendChild(script)
  }
})()
</script>

影像解碼

瀏覽器從下載好影像到顯示出來,中間還有一個解碼的過程,我們可以通過一個屬性將這個過程設定為非同步的

<img decoding="async" src="avatar-800w.jpg">

就這樣

響應式背景影像

根據裝置畫素比提供不同的背景影像,需要使用到 image-set 表示法,該方法可以提供多個影像路徑,並給每個路徑指定一個裝置畫素比

.selector {
  background-image: image-set(
    url(./photo-small.jpg) 1x,
    url(./photo-large.jpg) 2x);
}

指定裝置畫素比用 1x, 2x 這種方式,瀏覽器會根據裝置畫素比載入對應的影像,還可以為不支援瀏覽器提供後備內容

.selector {
  background-image: url(./photo-small.jpg);
  background-image: image-set(
    url(./photo-small.jpg) 1x,
    url(./photo-large.jpg) 2x);
}

所以主流瀏覽器的最新版本都支援,參考 https://caniuse.com/css-image-set,大部分瀏覽器需要新增 -webkit- 字首,更完整的寫法為

.selector {
  background-image: url(./photo-small.jpg);
  background-image: -webkit-image-set(
    url(./photo-small.jpg) 1x,
    url(./photo-large.jpg) 2x);
  background-image: image-set(
    url(./photo-small.jpg) 1x,
    url(./photo-large.jpg) 2x);
}

不過瀏覽器廠商字首更科學的方式是通過工具來新增,如 Autoprefixer

現代影像格式

使用現代格式的背景影像,還是使用 image-set 表示法,並指定一個影像格式,同時提供後備選項

.selector {
  background-image: url(./photo.jpg);
  background-image: image-set(
    url(./photo.avif) type("image/avif"),
    url(./photo.jpg) type("image/jpeg"));
}

同樣在 image-set 中,需要優先考慮的影像放在前面,目前支援 type 描述的瀏覽器並不多,能支援 type 描述的同樣也已經支援 AVIF 格式的影像了,所以上面的寫法其實有點多餘

不過因為可以為不支援的瀏覽器提供後備內容,所以可以放心使用

工具

相關文章