之前,整個《現代圖片效能優化及體驗優化指南》分了 5 篇來發,本文是系列合集,方便大家收藏及連貫閱讀。
圖片資源,在我們的業務中可謂是佔據了非常大頭的一環,尤其是其對頻寬的消耗是十分巨大的。
對圖片的效能優化及體驗優化在今天就顯得尤為重要。本文,就將從各個方面闡述,在各種新特性滿頭飛的今天,我們可以如何儘可能的對我們的圖片資源,進行效能優化及體驗優化。
首先,從圖片的型別上而言,除了常見的 PNG-8/PNG-24,JPEG,GIF 之外,我們更多的關注另外幾個較新的圖片格式:
首先,通過一張表格,快速過一下這幾個圖片,我們將從圖片型別、透明通道、動畫、編解碼效能、壓縮演演算法、顏色支援、記憶體佔用、相容性方面,對比它們:
圖片型別 | Alpha 通道 | 動畫 | 編解碼效能 | 壓縮演演算法 | 顏色支援 | 記憶體佔用 | 相容性 |
---|---|---|---|---|---|---|---|
GIF | 支援 | 支援 | 較高 | 無失真壓縮 | 索引色(256) | 基本一致 | ALL |
PNG-8/PNG-24 | 支援 | 不支援 | 較高 | 無失真壓縮 | 索引色(256)\直接色 | 基本一致 | ALL |
JPEG | 不支援 | 不支援 | 較高 | 有失真壓縮 | 直接色 | 基本一致 | ALL |
WebP | 支援 | 支援 | 編解碼效能差(低配裝置更為顯著) | 有失真壓縮\無失真壓縮 | 直接色 | 基本一致 | 高版本 Chrome\Opera\Android |
JPEG XL | 支援 | 支援 | 漸進式解碼 | 有失真壓縮\無失真壓縮 | 直接色 | 基本一致 | 部分高版本 Chrome\Opera\Firefox\Edge |
AVIF | 支援 | 支援 | 編解碼效能一般 | 有失真壓縮\無失真壓縮 | 直接色 | 基本一致 | 高版本 Chrome\Opera\Android\Edge |
首先,瞭解瞭解上述的一些引數含義:
當然,需要指出的是,Alpha 沒有透明度的意思,不代表透明度。opacity 和 transparency 才和透明度有關,前者是不透明度,後者是透明度。比如 css 中的「opacity: 0.5」就是設定元素有 50% 的不透明度。後來 Alvy Ray Smith 提出每個畫素再增加一個 Alpha 通道,取值為0到1,用來儲存這個畫素是否對圖片有「貢獻」,0代表透明、1代表不透明。也就是說,「Alpha 通道」儲存一個值,其外在表現是「透明度」,Alpha 和透明度沒啥關係
有失真壓縮演演算法是一種資料壓縮方法,經過此方法壓縮、解壓的資料會與原始資料不同但是非常接近。原理是藉由將次要的資訊資料捨棄,犧牲一些質量來減少資料量、提高壓縮比
無失真壓縮指資料經過壓縮後,資訊不受損失,還能完全恢復到壓縮前的原樣。無失真壓縮通常用於嚴格要求「經過壓縮、解壓縮的資料必須與原始資料一致」的場合。
索引顏色是一種以有限的方式管理數位影像顏色的技術,以節省計算機記憶體和檔案儲存,同時加速顯示重新整理和檔案傳輸。即用一個數位來代表(索引)一種顏色,在儲存圖片的時候,儲存一個數位的組合,同時儲存數位到圖片顏色的對映。這種方式只能儲存有限種顏色。索引色常見有1位(即黑白),8位元(即灰階/256色),16位元(即高彩),24位元(即真彩),30/36/48位元(即全綵)。
直接色使用四個數位來代表一種顏色,這四個數位分別代表這個顏色中紅色、綠色、藍色以及透明度(即 RGBA)。現在流行的顯示裝置可以在這四個維度分別支援256種變化,所以直接色可以表示2的32次方種顏色。
因為傳統的 PNG-8/PNG-24,JPEG,GIF 各自或多或少都存在一些問題,近些年來它們的替代方案之爭也愈演愈烈,核心領跑者可能是 WebP、JPEG XL、AVIF。
再簡單瞭解瞭解它們:
WebP 最初由 Google 在 2010 年 9 月釋出,其特性總結如下:
對於複雜的影象(比如照片)來說,WebP 無失真編碼表現並不好,但有損編碼表現卻非常棒。相近質量的圖片解碼速度 WebP 相距 JPEG 也已經相差不大了,而檔案壓縮比卻能提升不少。
下圖是我之前還在 TX 的時候做的一個測試對比:
載入同樣張數的 JPEG 與 WebP 的耗時對比:
對於 WebP 圖片格式,簡單做個總結:
截止至(2023-02-05)的相容性圖:
JPEG XL 由聯合影象專家組(開發原始 JPEG 標準的同一組織)於 2021 年釋出,旨在成為傳統 JPEG 的長期替代品。作為一種免版稅的開源標準,JPEG XL 的建立者希望其格式的開放效能夠吸引網路開發人員採用該標準,該格式的擴充套件名為 .jxl
,JXL 核心位元流於 2021 年 1 月凍結,檔案格式於 2021 年 4 月定稿。:
JPEG XL 中的 X 指 2000 年以來的多個 JPEG 標準的名稱:JPEG XT、JPEG XR、JPEG XS,而 L 表示 'long-term',表示「長期」。建立這種格式是為替換舊的JPEG檔案格式,並使用足夠長的時間。
其主要特點有:
看看同一張圖片,相同質量下的大小表現:
JPEG XL 是目前而言,最有可能全面替代傳統圖片格式(Gif、PNG、JPEG)的下一代標準,當然,在今天,需要看看其相容性:
好吧,目前的相容有點離譜。Chrome 從 91 版本開始已經實驗室性質支援了 .jxl
格式的圖片,需要通過 --enable-features=JXL
設定開啟,遺憾的是,從 Chrome 110 開始,Chrome 又不再支援 JPEG XL 。
有趣的是,Chrome 從 110 版本開始中棄用了對 JPEG-XL 的支援,谷歌的回答是,人們對 JPEG-XL 沒有足夠的興趣,並且與現有格式相比也沒有足夠的優勢。谷歌之前一直對 JPEG 的支援都是實驗性的性質的,他們認為 JPEG XL 缺乏生態系統支援,並且該格式沒有足夠多的好處(相對 WebP 和 AVIF)。也就是說,目前而言,Chrome 對 WebP 和 AVIF 等替代格式更感興趣。
最後,我們再來看看 AVIF 格式圖片。
AVIF 是由開放媒體聯盟 (AOM) 開發並於 2019 年釋出的另一種最新影象格式。該格式基於 AV1 視訊編解碼器,源自視訊幀。其特點如下:
看看 CaniUse 的資料:
下圖是 WebP vs JPEG XL vs AVIF 三者在圖片解碼上的效能表現:
從圖中可以看到,對於解碼效能的對比,結果居然是 WebP > AVIF > JPEG XL 。JPEG XL 的編解碼效能並沒有其描述的那麼強大。
總結一下,WebP、AVIF 和 JPEG XL 都是瀏覽器不廣泛支援的新型影象格式。雖然 WebP、AVIF 已經存在很長時間,但到今天,影響它們大規模使用的依舊是相容問題。它們各自有各自的特點與優勢,誰能勝出仍未知曉。
雖然 AVIF、JPEG XL 等新型圖片格式未得到任何瀏覽器的完全支援,但是在新版本的 Chrome、Firefox 和 Edge Chromium,可以使用設定標誌啟用對應影象格式,配合 HTML 的 Picture
標籤,我們還是可以一定程度上對我們的圖片進行格式選擇上的優化的。
這,就可以引出我們要說的第二部分 -- HTML Picture 標籤的使用。
HTML5 規範新增了 Picture Element。那麼 <picture>
元素的作用是什麼呢?
<picture>
元素通過包含零或多個 <source>
元素和一個 <img>
元素來為不同的顯示/裝置場景提供影象版本。瀏覽器會選擇最匹配的子 <source>
元素,如果沒有匹配的,就選擇 <img>
元素的 src 屬性中的 URL。然後,所選影象呈現在 <img>
元素佔據的空間中。
什麼意思呢?怎麼使用 <picture>
元素呢?
假設,沒有 <picture>
,只有 <img>
元素,我們想盡可能在支援一些現代圖片格式的瀏覽器上使用類似於上述我們提到的 WebP、AVIF 和 JPEG XL 等圖片格式,而不支援的瀏覽器回退使用常規的 JPEG、PNG 等。沒錯,就是一種漸進增強的思想,該怎麼辦呢?
只能是 JavaScript 去寫對應的邏輯,通過 JS 指令碼進行特性查詢,動態賦值給 <img>
的 src。
而有了 <picture>
後,瀏覽器將原生支援上述的一些列操作,我們來看看對應的語法:
<picture>
<!-- 可能是一些對相容性有要求的,但是效能表現更好的現代圖片格式-->
<source src="image.avif" type="image/avif">
<source src="image.jxl" type="image/jxl">
<source src="image.webp" type="image/webp">
<!-- 最終的兜底方案-->
<img src="image.jpg" type="image/jpeg">
</picture>
上述程式碼的含義是:
source
元素指向新 AVIF 格式的影象。如果瀏覽器支援 AVIF 影象,那麼它會選擇該影象檔案。否則,它將移動到下一個 source
元素。source
元素指向新 JPEG XL 格式的影象。如果瀏覽器支援 JPEG XL 影象,那麼它會選擇該影象檔案。否則,它將移動到下一個 source
元素。source
元素指向一張WebP 格式的影象。如果瀏覽器能夠渲染 WebP 影象,它將使用該影象檔案。img
元素 src 屬性中的影象檔案。img 元素指向的是 JPEG 格式的圖片,它是最終的兜底方案。這意味著現在我們可以在不犧牲向後相容性的情況下開始使用新的影象格式。
簡而言之,<picture>
元素的作用:
<source>
給出一系列對相容性有所要求的現代圖片格式選項<img>
給出兜底的高相容性圖片格式選項<source>
的 srcset、media 和 type 屬性,來選擇最匹配頁面當前佈局、顯示裝置特徵等的相容影象。<img>
元素佔據的空間中總結一下,本文對常見的圖片格式以及最新的幾種未被大規模相容的圖片格式進行的對比,它們分別是:
其後,著重介紹了 3 種現代圖片格式:WebP、JPEG XL、AVIF。相對於 JPEG 等傳統格式,它們在色彩表現、動畫支援、是否支援無失真有失真壓縮、壓損比率、編解碼效能上有著更進一步的提升,正在成為下一階段 Web 影象的標準。
最後,介紹了 <picture>
元素,藉助它,我們能更好的實現圖片的漸進增強。
下一個模組,我們來看看圖片資源如何更好的適配不同的螢幕尺寸。
這裡首先會涉及一個預備知識,螢幕的 DPR 值,那麼,什麼是 DPR 呢?要了解 DPR,又需要知道什麼是裝置獨立畫素 以及 物理畫素。
以 iPhone6/7/8為例,這裡我們開啟 Chrome 開發者工具:
這裡的 375 * 667
表示的是什麼呢,表示的是裝置獨立畫素(DIP),也可以理解為 CSS 畫素,也稱為邏輯畫素:
裝置獨立畫素 = CSS 畫素 = 邏輯畫素
如何記憶呢?這裡使用 CSS 畫素來記憶,也就是說。我們設定一個寬度為 375px 的 div,剛好可以充滿這個裝置的一行,配合高度 667px ,則 div 的大小剛好可以充滿整個螢幕。
OK,那麼,什麼又是物理畫素呢。我們到電商網站購買手機,都會看一看手機的引數,以 JD 上的 iPhone7 為例:
可以看到,iPhone7 的解析度是 1334 x 750
,這裡描述的就是螢幕實際的物理畫素。
物理畫素,又稱為裝置畫素。顯示屏是由一個個物理畫素點組成的,1334 x 750
表示手機分別在垂直和水平上所具有的畫素點數。通過控制每個畫素點的顏色,就可以使螢幕顯示出不同的影象,螢幕從工廠出來那天起,它上面的物理畫素點就固定不變了,單位為pt。
裝置畫素 = 物理畫素
OK,有了上面兩個概念,就可以順理成章引出下一個概念。DPR(Device Pixel Ratio) 裝置畫素比,這個與我們通常說的視網膜屏(多倍屏,Retina屏)有關。
裝置畫素比描述的是未縮放狀態下,物理畫素和裝置獨立畫素的初始比例關係。
簡單的計算公式:
DPR = 物理畫素 / 裝置獨立畫素
我們套用一下上面 iPhone7 的資料(取裝置的物理畫素寬度與裝置獨立畫素寬度進行計算):
iPhone7’s DPR = iPhone7’s 物理畫素寬度 / iPhone7's 裝置獨立畫素寬度 = 2
750 / 375 = 2
或者是 1334 / 667 = 2
可以得到 iPhone7 的 dpr 為 2。也就是我們常說的視網膜螢幕。
視網膜(Retina)螢幕是蘋果公司"發明"的一個行銷術語。 蘋果公司將 dpr > 1
的螢幕稱為視網膜螢幕。
在視網膜螢幕中,以 dpr = 2 為例,把 4(2x2) 個畫素當 1 個畫素使用,這樣讓螢幕看起來更精緻,但是元素的大小本身卻不會改變:
OK,我們再來看看 iPhone XS Max:
它的物理畫素如上圖是 2688 x 1242
,
它的 CSS 畫素是 896 x 414
,很容易得出 iPhone XS Max 的 dpr 為 3。
那麼,DPR 和圖片適配有什麼關係呢?
舉個例子,同樣的 CSS 畫素大小下,螢幕如果有不同 DPR,同樣大小的圖片渲染出來的效果不盡相同。
我們以 dpr = 3
的手機為例子,在 300 x 389
CSS 畫素大小的範圍內,渲染 1倍/2倍/3倍 圖的效果如下:
實際圖片所佔的物理畫素為 900 x 1167。
可以看到,在高 DPR 裝置下提供只有 CSS 畫素大小的圖片,是非常模糊的。
因此,為了在不同的 DPR 螢幕下,讓圖片看起來都不失真,我們需要為不同 DPR 的圖片,提供不同大小的圖片。
那麼,有哪些可行的解決方案呢?
假設,在行動端假設我們需要一張 CSS 畫素為 300 x 200
的影象,考慮到現在已經有了 dpr = 3 的裝置,那麼要保證圖片在 dpr = 3 的裝置下也正常高清展示,我們最大可能需要一張 900 x 600
的原圖。
這樣,不管裝置的 dpr 是否為 3,我們統一都使用 3 倍圖。這樣即使在 dpr = 1,dpr = 2 的裝置上,也能非常好的展示圖片。
當然這樣並不可取,會造成大量頻寬的浪費。
現代瀏覽器,提供了更好的方式,讓我們能夠根據裝置 dpr 的不同,提供不同尺寸的圖片。
方案二,我們可以考慮使用媒體查詢。到今天,我們可以通過相應的媒體查詢,得知當前的裝置的 DPR 值,這樣,我們就可以在對應的媒體查詢中,使用對應的圖片。
像是這樣:
#id {
background: url([email protected])
}
@media (device-pixel-ratio: 2) {
#id {
background: url([email protected])
}
}
@media (device-pixel-ratio: 3) {
#id {
background: url([email protected])
}
}
這個方案的缺點在於:
-webkit-min-device-pixel-ratio
,當然這個可以由 autoprefixer
輔助解決image-set
屬於 CSS background 中的一種語法,image-set()
函數為裝置提供最合適的影象解析度,它提供一組影象選項,每個選項都有一個相關的 DPR 宣告,瀏覽器將從中選擇最適合裝置的影象進行設定。
什麼意思呢,來看看程式碼:
.img {
/* 不支援 image-set 的瀏覽器*/
background-image: url('../[email protected]');
/* 支援 image-set 的瀏覽器*/
background-image: image-set(
url('./[email protected]') 2x,
url('./[email protected]') 3x
);
}
這樣一看,作用應該很清晰了。對於支援 image-set
語法的瀏覽器:
url('./[email protected]') 2x
記錄,也就是最終生效的 URL 是 './[email protected]'
;url('./[email protected]') 3x
記錄,也就是最終生效的 URL 是 './[email protected]'
;其中的 2x
,3x
就是用於匹配 DRP的。
使用 image-set
的一些痛點與媒體查詢方案類似。程式碼量與相容性語法,而且難以匹配所有情況。
簡單來說,srcset 可以根據不同的 dpr 拉取對應尺寸的圖片:
<div class='illustration'>
<img src='illustration-small.png'
srcset='images/illustration-small.png 1x,
images/illustration-big.png 2x'
>
</div>
上面 srcset
裡的 1x,2x 表示 畫素密度描述符,表示
images/illustration-small.png
這張圖images/illustration-big.png
這張圖srcset
語法,src='illustration-small.png'
將會是最終的兜底方案上面 1x,2x 的寫法比較容易接受易於理解。
但是,上述 3 種方案都存在統一的問題,只考慮了 DPR,但是忽略了響應性佈局的複雜性與螢幕的多樣性。
因此,規範還推出了一種方案 -- srcset 屬性配合 sizes 屬性 w 寬度描述符。
srcset
屬性還有一個 w 寬度描述符,配合 sizes
屬性一起使用,可以覆蓋更多的面。
sizes
屬性怎麼理解呢?它定義影象元素在不同的視口寬度時,可能的大小值。
以下面這段程式碼為例子:
<img
sizes = 「(min-width: 600px) 600px, 300px"
src = "photo.png"
srcset = 「[email protected] 300w,
[email protected] 600w,
[email protected] 1200w,
>
解析一下:
sizes = 「(min-width: 600px) 600px, 300px"
的意思是:
也就是 sizes 屬性宣告了在不同寬度下圖片的 CSS 寬度表現。這裡可以理解為,大螢幕下圖片寬度為 600px,小螢幕下圖片寬度為 300px。
需要注意的是,這裡大屏、小屏下圖片具體的寬度表現,還是需要藉助媒體查詢程式碼,經由 CSS 實現的
srcset = 「[email protected] 300w, [email protected] 600w, [email protected] 1200w
裡面的 300w,600w,900w 叫寬度描述符。
那麼,怎麼確定當前場景會選取哪張圖片呢?
當前螢幕 CSS 寬度為 375px,則圖片 CSS 寬度為 300px。分別用上述 3 個寬度描述符的數值除以 300。
上面計算得到的 1、 2、 4 即是算出的有效的畫素密度,換算成和 x 描述符等價的值 。這裡 600w 算出的 2 即滿足 dpr = 2 的情況,選擇此張圖。
當前螢幕 CSS 寬度為 414px,則圖片 CSS 寬度仍為 300px。再計算一次:
因為 dpr = 3,2 已經不滿足了,則此時會選擇 1200w 這張圖。
當前螢幕 CSS 寬度為 1920px,則圖片 CSS 寬度變為了 600px。再計算一次:
因為 dpr = 1,所以此時會選擇 600w 對應的圖片。
具體的可以試下這個 Demo:CodePen Demo -- srcset屬性配合w寬度描述符配合sizes屬性
此方案的意義在於考慮到了響應性佈局的複雜性與螢幕的多樣性,利用上述規則,可以一次適配 PC 端大螢幕和行動端高清屏,一箭多雕。
嗯,總結一下,在實現響應式影象時,我們同時使用 srcset
和 sizes
屬性。它們的作用是:
srcset
:定義多個不同寬度的影象源,讓瀏覽器在 HTML 解析期間選擇最合適的影象源sizes
:定義影象元素在不同的視口寬度時,可能的大小值有了這些屬性後,瀏覽器就會根據 srcset/size 來建立一個解析度切換器的響應式圖片,可以在不同的解析度的情況下,提供相同尺寸的影象,或者在不同的檢視大小的情況下,提供不同尺寸大小的影象。
本章節一共列舉了 5 種實現響應式圖片,適配不同螢幕大小,不同 DPR 的方式,它們分別是:
image-set
合理使用它們,可以有效的為不同螢幕,提供最為恰當的圖片資源,在保證使用者體驗的同時,儘可能節省頻寬。
它們各有優缺點,可以根據自己實際的業務場景,選取合適相對成本最低的方案,並且適當的配合 Autoprefixer 以及一些 PostCSS 等工具,簡化程式碼量。
OK,下面進入到我們的第三個模組,圖片的寬高比、裁剪與縮放。我們會介紹 4 個新的特性:
aspect-ratio
object-fit
object-position
image-rendering
aspect-ratio
避免佈局偏移很多時候,只能使用固定尺寸大小的圖片,我們的佈局可能是這樣:
對應的佈局:
<ul class="g-container">
<li>
<img src="http://placehold.it/150x100">
<p>圖片描述</p>
</li>
</ul>
ul li img {
width: 150px;
}
當然,萬一假設後端介面出現一張非正常大小的圖片,上述不加保護的佈局就會出問題:
所以對於圖片,我們總是建議同時寫上高和寬,避免因為圖片尺寸錯誤帶來的佈局問題:
ul li img {
width: 150px;
height: 100px;
}
同時,給 <img>
標籤同時寫上高寬,可以在圖片未載入之前提前佔住位置,避免圖片從未載入狀態到渲染完成狀態高寬變化引起的重排問題。
當然,到今天,我們還可以使用 aspect-ratio
設定圖片的高寬比。
aspect-ratio
CSS 屬性為容器規定了一個期待的寬高比,這個寬高比可以用來計算自動尺寸以及為其他佈局函數服務。
像是上面的程式碼,我們就可以替換成:
ul li img {
width: 150px;
aspect-ratio: 3 / 2;
}
當然,有的時候,我們的佈局是響應式動態在變化的,容器的寬度也是不確定的,因此,有了 aspect-ratio
之後,我們的寫法就可以更佳的舒服。
ul li img {
width: 100%;
aspect-ratio: 3 / 2;
}
這裡,容器基於 Flex 彈性佈局或者響應式佈局,其寬度是不固定的,但是圖片的寬高比是固定的,使用 aspect-ratio: 3 / 2
就能非常好的適配這種情況。
我們藉助了 aspect-ratio 這個 CSS 中較新的屬性來始終自動獲得正確的寬高比,無論其父元素的寬度如何變化。
當然,
aspect-ratio
不僅僅只是能運用在這裡,在aspect-ratio
出現之前,我們只能通過一些其它的 Hack 方式,譬如設定padding-top
等方式模擬固定的寬高比。在aspect-ratio
之後,我們終於有了設定容器固定寬高比的能力。
object-fit
避免圖片拉伸當然,限制高寬也會出現問題,譬如圖片被拉伸了,非常的難看:
這個時候,我們可以藉助 object-fit
,它能夠指定可替換元素的內容(也就是圖片)該如何適應它的父容器的高寬。
ul li img {
width: 150px;
aspect-ratio: 3 / 2;
object-fit: cover;
}
利用 object-fit: cover
,使圖片內容在保持其寬高比的同時填充元素的整個內容框。
object-fit
的取值有 fill
、none
、contain
、cover
,與 background-size
類似,可以類比記憶。
也可以看看這張圖,很易於理解:
object-fit
還有一個配套屬性 object-position
,它可以控制圖片在其內容框中的位置。(類似於 background-position
),預設是 object-position: 50% 50%
,如果你不希望圖片居中展示,可以使用它去改變圖片實際展示的 position。
ul li img {
width: 150px;
aspect-ratio: 3 / 2;
object-fit: cover;
object-position: 50% 100%;
}
像是這樣,object-position: 100% 50%
指明從底部開始展示圖片。這裡有一個很好的 Demo 可以幫助你理解 object-position
。
CodePen Demo -- Object position
image-rendering
設定圖片縮放演演算法相對於上面幾個新特性,image-rendering
會更為冷門。
很多時候,我們設定一個圖片在頁面上的展示大小為 200px x 200px
,但是圖片的原始尺寸可能是 800px x 800px
,也可能是 50px x 50px
。
這個時候,我們就可以利用 image-rendering
,設定圖片在縮放狀態下的展示演演算法。
image-rendering
在特定的場景下,能夠起到奇效。
來看這樣一個有意思的 DEMO,假設我們有這樣一個原圖效果,它是一個二維條碼,大小為 100px x 100px
:
如果我們將它放大,放到很大,明顯,這個二維條碼會失真,像是這樣:
OK,在這種放大失真的情況想,可以使用 image-rendering
改變圖片縮放演演算法,這裡我們試一下 image-rendering: pixelated
:
.img {
image-rendering: pixelated;
}
效果變化,如下圖所示:
可以看到,image-rendering: pixelated
處理過的影象,竟然變得如此清晰!
CodePen Demo -- QrCode Image-rendering demo
來看看 image-rendering
的幾個取值:
image-rendering: auto
:自 Gecko 1.9(Firefox 3.0)起,Gecko 使用雙線性(bilinear)演演算法進行重新取樣(高質量)。image-rendering: smooth
:使用能最大化影象客觀觀感的演演算法來縮放影象image-rendering: high-quality
:與 smooth 相同,但更傾向於高質量的縮放。image-rendering: crisp-edges
:必須使用可有效保留對比度和影象中的邊緣的演演算法來對影象進行縮放,並且,該演演算法既不會平滑顏色,又不會在處理過程中為影象引入模糊。合適的演演算法包括最近鄰居(nearest-neighbor)演演算法和其他非平滑縮放演演算法,比如 2×SaI 和 hqx-* 系列演演算法。此屬性值適用於畫素藝術作品,例如一些網頁遊戲中的影象。image-rendering: pixelated
:放大影象時,使用最近鄰居演演算法,因此,影象看著像是由大塊畫素組成的。縮小影象時,演演算法與 auto 相同。雖然規範定義了挺多值,但是實際上,現代瀏覽器基本暫時只支援:auto
、pixelated
、以及 -webkit-optimize-contrast
(Chrome 下的 smooth)。
看描述都會挺懵逼的,實際使用的時候,最好每個都試一下驗證一下效果。總結而言,image-rendering
的作用是在影象縮放時,提供不一樣的渲染方式,讓圖片的展示形態更為多樣化,或者說是儘可能的去減少圖片的失真帶來的資訊損耗。
我們再看一個 DEMO,原圖如下(例子來源於 W3C 規範檔案):
實際效果:
當然,看上去 pixelated
的效果挺好,這是由於這是一張偏向於向量的圖片,細節不多,對於高精度的人物圖,就不太適用於 pixelated
,容易把圖片馬賽克化。
真正規範希望的在放大後讓圖片儘可能不失真的 crisp-edges
效果,目前暫時沒有得到瀏覽器的實現。後面可以期待一下。
CodePen Demo -- Image-rendering demo
這一章,我們介紹了 4 個較新的 CSS 特性:
aspect-ratio
:控制容器的寬高比,避免產生布局偏移及抖動object-fit
:設定內容應該如何適應到其使用高度和寬度確定的框,避免圖片拉伸object-position
:基於 object-fit
,設定圖片實際展示的 position 範圍image-rendering
:控制圖片在縮放狀態下的展示演演算法合理利用它們,可以給使用者在圖片上以更好的體驗。
繼續下一個章節。本章節,我們來討論下圖片的懶載入與非同步影象解碼方案。
懶載入是一種網頁效能優化的常見方式,它能極大的提升使用者體驗。到今天,現在一張圖片超過幾 M 已經是常見事了。如果每次進入頁面都需要請求頁面上的所有的圖片資源,會較大的影響使用者體驗,對使用者的頻寬也是一種極大的損耗。
所以,圖片懶載入的意義即是,當頁面未捲動到相應區域,該區域內的圖片資源(網路請求)不會被載入。反之,當頁面捲動到相應區域,相關圖片資源的請求才會被髮起。
在過去,我們通常都是使用 JavaScript 方案進行圖片的懶載入。而今天,我們在圖片的懶載入實現上,有了更多不一樣的選擇。
首先,回顧一下過往最常見的,使用 JavaScript 方案實現圖片的懶載入。
通過 JavaScript 實現的懶載入,主要是兩種方式:
getBoundingClientRect
API 獲取元素圖片距離視口頂部的距離,配合當前可視區域的位置實現圖片的懶載入IntersectionObserver
API,Intersection Observer(交叉觀察器) 配合監聽元素的 isIntersecting
屬性,判斷元素是否在可視區內,能夠實現比監聽 onscroll 效能更佳的圖片懶載入方案但是,JavaScript 方案的一個劣勢在於,不管如何,需要引入一定量的 JavaScript 程式碼,進行一定量的運算。
到今天,其實我們有更多的其他便捷的方式去實現圖片的懶載入。
content-visibility: auto
實現圖片內容的延遲渲染首先,介紹一個非常有用,但是相對較為冷門的屬性 -- content-visibility
。
content-visibility
:屬性控制一個元素是否渲染其內容,它允許使用者代理(瀏覽器)潛在地省略大量佈局和渲染工作,直到需要它為止。
利用 content-visibility
的特性,我們可以實現如果該元素當前不在螢幕上,則不會渲染其後代元素。
假設我們有這樣一個 DEMO:
<div class="g-wrap">
// 模組 1
<div class="paragraph">
<p>Lorem Start!</p>
<img src="https://s1.ax1x.com/2023/02/20/pSX1xMV.png">
那麼,在新增了 content-visibility: auto
之後,注意觀察頁面的卷軸及捲動效果:
可以看到卷軸在向下捲動在不斷的抽搐,這是由於下面不在可視區域內的內容,一開始是沒有被渲染的,在每次捲動的過程中,才逐漸渲染,以此來提升效能。
Codepen Deom -- content-visibility: auto Image Load Demo
content-visibility: auto
VS 圖片懶載入
當然,其實使用 content-visibility: auto
並不能真正意義上實現圖片的懶載入。
這是因為,即便當前頁面可視區域外的內容未被渲染,但是圖片資源的 HTTP/HTTPS 請求,依然會在頁面一開始被觸發!
因此,這也得到了一個非常重要的結論:
content-visibility: auto
無法直接替代圖片懶載入,設定了 content-visibility: auto
的元素在可視區外只是未被渲染,但是其中的靜態資源仍舊會在頁面初始化的時候被全部載入。因此,它更像是一個虛擬列表的替代方案。
關於 content-visibility
本文限於篇幅,沒有完全展開,但是它是一個非常有意思且對渲染效能有幫助的屬性,完整的教學,你可以看我的這篇文章 -- 使用 content-visibility 優化渲染效能
使用 loading=lazy
HTML 屬性實現圖片懶載入
OK,content-visibility
很不錯,但是略有瑕疵。但是,我們還有其他方式。
HTML5 新增了一個 loading
屬性。
到今天,除了 IE 系列瀏覽器,目前都支援通過 loading
屬性實現延遲載入。此屬性可以新增到 <img>
元素中,也可以新增到 <iframe>
元素中。
屬性的值為 loading=lazy
會告訴瀏覽器,如果影象位於可視區時,則立即載入影象,並在使用者捲動到它們附近時獲取其他影象。
我們可以像是這樣使用它:
<img src="xxx.png" loading="lazy">
這樣,便可以非常便捷的實現圖片的懶載入,省去了新增繁瑣的 JavaScript 程式碼的過程。
看看 loading=lazy
到今天(2023-02-26)的相容性,還是非常不錯的:
使用 decoding=async
實現圖片的非同步解碼
除了 loading=lazy
,HTML5 還新增了一個非常有意思的屬性增強圖片的使用者體驗。那就是 decoding
屬性。
HTMLImageElement 介面的 decoding
屬性用於告訴瀏覽器使用何種方式解析影象資料。
它的可選取值如下:
sync
: 同步解碼影象,保證與其他內容一起顯示。
async
: 非同步解碼影象,加快顯示其他內容。
auto
: 預設模式,表示不偏好解碼模式。由瀏覽器決定哪種方式更適合使用者。
上文其實也提及了,瀏覽器在進行圖片渲染展示的過程中,是需要對圖片檔案進行解碼的,這一個過程快慢與圖片格式有關。
而如果我們不希望圖片的渲染解碼影響頁面的其他內容的展示,可以使用 decoding=async
選項,像是這樣:
<img src="xxx.png" decoding="async">
這樣,瀏覽器便會非同步解碼影象,加快顯示其他內容。這是圖片優化方案中可選的一環。
同樣的,我們來看看到今天(2023-02-26),decoding="async"
的相容性,整體還是非常不錯的,作為漸進增強方案使用,是非常好的選擇。
實際檢驗 loading=lazy
與 decoding=async
效果
OK,下面我們製作一個簡單的 DEMO,試一下 loading=lazy
與 decoding=async
的效果。
我們準備一個擁有 339 個圖片的 HTML 頁面,每個圖片檔案的 src 大小不一。
<div class="g-container">
<img src="image1.jpeg">
<img src="image2.jpeg">
// ... 339 個
</div>
CSS 的設定也很重要,由於是純圖片頁面,如果不給圖片設定預設高寬,最頁面重新整理的一瞬間,<img>
元素的高寬都是 0,會導致所有 <img>
元素都在可視區內,所以,我們需要給 <img>
設定一個預設的高寬:
img {
margin: 8px;
width: 300px;
height: 200px;
object-fit: cover;
}
這樣,再不新增 loading=lazy
與 decoding=async
的狀態下,看看 Network
的表現:
我這裡沒有模擬弱網環境,網速非常快,可以看到,傳送了 339 個圖片資源請求,也就是全部的圖片資源在頁面載入的過程中都請求了,頁面 Load
事件完成的時間為 1.28s。
好,我們給所有的圖片元素,新增上 loading=lazy
與 decoding=async
:
<div class="g-container">
<img src="image1.jpeg" loading="lazy" decoding="async">
<img src="image2.jpeg" loading="lazy" decoding="async">
// ... 339 個
</div>
看看效果:
可以看到,這一次只傳送了 17 個圖片資源請求,頁面 Load
事件完成的時間為 26ms。
優化前
優化後
1.28s
26 ms
1.28s 到 26ms,效果是非常明顯的,如果是弱網環境,對首屏載入效能的提升,會更為明顯!
當然,實際我測試的過程也,也單獨試過 decoding="async"
的作用,只是由於是純圖片頁面,效果不那麼明顯。感興趣的同學,可以自行嘗試。
模組總結
在本章節中,我們介紹了不同的方式實現圖片的懶載入、延遲渲染、非同步解碼,它們分別是:
- 通過 onscroll 事件與
getBoundingClientRect
API 實現圖片的懶載入方案
- 通過 Intersection Observer(交叉觀察器)實現比監聽 onscroll 效能更佳的圖片懶載入方案
- 通過
content-visibility: auto
實現圖片資源的延遲渲染
- 通過
loading=lazy
HTML 屬性實現圖片懶載入
- 通過
decoding=async
HTML 屬性實現圖片的非同步解碼
圖片資源的容錯及可存取性處理
OK,最後一個章節,我們簡單聊一聊圖片資源的容錯及可存取性處理。
圖片的可存取性處理
可存取性(A11Y),在我們的網站中,屬於非常重要的一環,但是大部分同學都容易忽視它。
在一些重互動、重邏輯的網站中,我們需要考慮使用者的使用習慣、使用場景,從高可存取性的角度考慮,譬如假設使用者沒有滑鼠,僅僅使用鍵盤,能否順暢的使用我們的網站?
非常重要的一點是,提高可存取性也能讓普通使用者更容易理解 Web 內容。
基於 Usability & Web Accessibility - image
對於影象資訊,我們需要大致遵循如下可存取性原則:
- 所有有意義的 img 元素必須有 alt 屬性
- 提供替代 alt 屬性的其他方式
- 使用輔助技術隱藏裝飾影象
第一點非常好理解,所有的有意義的圖片元素都必須要提供 alt
屬性。
第二點比較有意思,在 A11Y 中,其實有一套 WAI-ARIA 標準。WAI-ARIA 是一個為殘疾人士等提供無障礙存取動態、可互動Web內容的技術規範。
簡單來說,它提供了一些屬性,增強標籤的語意及行為:
- 可以使用 tabindex 屬性控制元素是否可以聚焦,以及它是否/在何處參與順序鍵盤導航
- 可以使用 role 屬性,來標識元素的語意及作用,譬如使用
<div id="saveChanges" tabindex="0" role="button">Save</div>
來模擬一個按鈕
- 還有大量的
aria-*
屬性,表示元素的屬性或狀態,幫助我們進一步地識別以及實現元素的語意化,優化無障礙體驗
上述第二點,提供替代 alt 屬性的其他方式 的含義就是使用 WAR-ARIA 規範提供的諸如 aria-label
和 aria-labelledby
屬性為影象提供可存取的名稱。
當存在這些屬性時,輔助技術(螢幕閱讀器)將忽略影象的 alt
屬性並讀取 ARIA 標籤。
而第三點,使用輔助技術隱藏裝飾影象,又是什麼意思呢?
上面第一點 所有有意義的 img 元素必須有 alt 屬性,反過來說,頁面上也會存在無意義的裝飾性的圖片,這些圖片內容對輔助技術(螢幕閱讀器)而言,其實是可以忽略的。
對於沒有任何功能或資訊內容的裝飾影象,可以通過多種方式對螢幕閱讀器隱藏:
- 使用空的
alt
屬性
- 使用 ARIA 屬性
role="presentation"
標明圖片元素是裝飾可忽略圖片
- 使用 CSS background 的方式呈現這些圖片
alt 不要與 title 混淆
OK,下面來講一些有意思的細節內容。
有一個非常基礎的知識,簡單過一下,也就是圖片元素中,alt
與 title
的差異:
- 圖片中的
alt
屬性是在圖片不能正常顯示時出現的文字提示。
- 圖片中的
title
屬性是在滑鼠在移動到元素上的文字提示。
正確使用 alt 屬性
對於使用螢幕閱讀器的使用者而言,圖片是無法正常展示或者被的瀏覽的,基於此,我們需要利用好 alt
屬性,或者是上述的aria-label
和 aria-labelledby
屬性。
那麼,這些屬性內的內容應該填充什麼呢?我們需要基於圖片的功能加以區分:
-
資訊性影象:以圖形方式表示概念和資訊的影象,通常是圖片、照片和插圖。alt
替代文字應該至少是一個簡短的描述,傳達影象所呈現的基本資訊。
-
裝飾性影象:當影象的唯一目的是為頁面新增視覺裝飾,而不是傳達對理解頁面很重要的資訊時,如上述所言,使用空的 alt,譬如 alt=""
-
功能影象:用作連結或按鈕的影象的替代文字應該描述連結或按鈕的功能,而不是視覺影象。此類影象的範例是表示列印功能的印表機圖示或提交表單的按鈕。
-
文字影象:可讀文字有時會出現在影象中。如果圖片不是徽標,請避免圖片中出現文字。但是,如果使用文字影象,替代文字應包含與影象中相同的詞。
-
-
影象組:如果多張影象傳達一條資訊,則一張影象的替代文字應傳達整組資訊。
-
影象對映:包含多個可點選區域的影象的替代文字應該為連結集提供整體上下文。此外,每個可單獨點選的區域都應該有替代文字來描述連結的目的或目的地。
其實 alt
的學問是非常之多的,如果我們的頁面能做到這一點,那真的算是從根上開始思考,開始優化使用者體驗。
img 元素與 background 元素的取捨
OK,那麼,講到這裡,還有一個有意思的點就很自然的應該被提及。
那就是我們應該什麼時候使用 <img>
元素,什麼時候使用 background
內嵌圖片?
我們可以從效能及功能兩個方面進行考慮:
型別
img
backgroud-image
圖層位置
前景
背景
預設初始尺寸
不定
固定
是否會產生迴流重繪
會
不會
圖片載入失敗
可以觸發元素的 onerror 事件,展示 alt 屬性
無法有效設定例外處理場景
使用場景
Logo、產品圖片、廣告圖片
裝飾性無語意內容等
其實效能上並不是核心考慮的點,因為上文我們也講到了在今天可以大規模使用是 loading="lazy"
屬性,圖片可以進行原生支援的懶載入。
我們在考慮選取 <img>
還是 backgroud-image
的時候,更多的還是從圖片功能上進行考慮。一般來說,作為修飾的且無語意的裝飾性圖片選擇使用 background-image
,而比較重要的與網頁內容相關的就使用 <img>
標籤。
由於有語意的圖片使用 <img>
展示,它的一個好處在於,當圖片載入失敗的時候,可以觸發元素的 onerror 事件,我們可以有效的利用這一點,對圖片進行例外處理。
圖片的例外處理
當圖片連結掛了,載入失敗了,我們比較好的處理方式應該是怎麼樣呢?
處理的方式有很多種。在張鑫旭老師的這篇文章中 -- 圖片載入失敗後CSS樣式處理最佳實踐 有一個不錯的實踐。
核心思路為:
- 利用圖片載入失敗,觸發
<img>
元素的 onerror
事件,給載入失敗的 <img>
元素新增一個樣式類
- 利用新增的樣式類,配合
<img>
元素的偽元素,在展示預設兜底圖的同時,還能一起展示 <img>
元素的 alt
資訊
<img src="test.png" alt="Alt Info" onerror="this.classList.add('error');">
img.error {
position: relative;
display: inline-block;
}
img.error::before {
content: "";
/** 定位程式碼 **/
background: url(error-default.png);
}
img.error::after {
content: attr(alt);
/** 定位程式碼 **/
}
我們利用偽元素 before
,載入預設錯誤兜底圖,利用偽元素 after
,展示圖片的 alt
資訊:
OK,到此,完整的對圖片的處理就算完成了,這也比較好的闡述了為什麼,對有語意,有 alt
資訊的圖片,我們應該使用 <img>
元素來實現。這是因為,我們可以在錯誤發生的時候,比較好的對圖片進行兜底展示,讓使用者直觀的能夠看到 alt 內容。
完整的 Demo 你可以戳這裡看看:
當然,上述方案存在兩個小問題:
- 對於每一個
<img>
元素,我們都需要寫一段 onerror="this.classList.add('error');"
程式碼,有點重複。因此,這個工作也可以交給 JavaScript 全域性性的完成,並且,我們可能需要判斷 alt
的值是否為空,在為空時,使用預設圖片 alt 兜底文案。
- 早年間,
<img>
等替換元素是沒有偽元素的,後面 Chrome/Firefox 瀏覽器逐漸支援了當,<img>
的 src 拉取失敗時,支援 <img>
元素的偽元素展示,這才有了上述的方案,但是,目前 Safari 仍不支援這個特性,所以,在 Safari 下,我們可能得到如下的結果:
效果仍然還是 OK 的,只是沒有了兜底圖的展示,在實際使用過程中,需要知道這一點。
總結一下
本章節,對圖片資源的容錯及可存取性處理進行了闡述。核心內容在於:
- 對於影象資訊,我們需要大致遵循如下可存取性原則:
- 所有有意義的 img 元素必須有 alt 屬性
- 提供替代 alt 屬性的其他方式
- 使用輔助技術隱藏裝飾影象
- 正確使用 alt 屬性,瞭解不同場景下 alt 應該填充什麼內容
- img 元素與 background 元素的取捨
- 圖片例外處理的最佳實踐
至此,整個現代圖片效能優化及體驗優化指南到此就圓滿結束,整個系列的文章囊括了非常多的新的規範及特性,需要大家在實踐中根據實際情況靈活選取使用。
同時,我們也應該能看到,前端技術僅僅在這一小個領域,都在不斷的迭代創新。雖然很難,還是需要不斷充實自己跟上新的潮流。共勉。
最後
OK,本文到此結束,希望本文對你有所幫助