《現(xiàn)代圖片性能優(yōu)化及體驗(yàn)優(yōu)化指南》全集【建議收藏】
在之前,《現(xiàn)代圖片性能優(yōu)化及體驗(yàn)優(yōu)化指南》分了 5 篇發(fā)出,本文是全部 5 篇的合集,方便大家收藏閱讀。
圖片資源,在我們的業(yè)務(wù)中可謂是占據(jù)了非常大頭的一環(huán),尤其是其對(duì)帶寬的消耗是十分巨大的。
對(duì)圖片的性能優(yōu)化及體驗(yàn)優(yōu)化在今天就顯得尤為重要。本文,就將從各個(gè)方面闡述,在各種新特性滿(mǎn)頭飛的今天,我們可以如何盡可能的對(duì)我們的圖片資源,進(jìn)行性能優(yōu)化及體驗(yàn)優(yōu)化。
圖片類(lèi)型的選取及 Picture 標(biāo)簽的使用
首先,從圖片的類(lèi)型上而言,除了常見(jiàn)的 PNG-8/PNG-24,JPEG,GIF 之外,我們更多的關(guān)注另外幾個(gè)較新的圖片格式:
- WebP
- JPEG XL
- AVIF
首先,通過(guò)一張表格,快速過(guò)一下這幾個(gè)圖片,我們將從圖片類(lèi)型、透明通道、動(dòng)畫(huà)、編解碼性能、壓縮算法、顏色支持、內(nèi)存占用、兼容性方面,對(duì)比它們:
| 圖片類(lèi)型 | Alpha 通道 | 動(dòng)畫(huà) | 編解碼性能 | 壓縮算法 | 顏色支持 | 內(nèi)存占用 | 兼容性 |
|---|---|---|---|---|---|---|---|
| GIF | 支持 | 支持 | 較高 | 無(wú)損壓縮 | 索引色(256) | 基本一致 | ALL |
| PNG-8/PNG-24 | 支持 | 不支持 | 較高 | 無(wú)損壓縮 | 索引色(256)\直接色 | 基本一致 | ALL |
| JPEG | 不支持 | 不支持 | 較高 | 有損壓縮 | 直接色 | 基本一致 | ALL |
| WebP | 支持 | 支持 | 編解碼性能差(低配設(shè)備更為顯著) | 有損壓縮\無(wú)損壓縮 | 直接色 | 基本一致 | 高版本 Chrome\Opera\Android |
| JPEG XL | 支持 | 支持 | 編解碼性能優(yōu)于 WebP | 有損壓縮\無(wú)損壓縮 | 直接色 | 基本一致 | 部分高版本 Chrome\Opera\Firefox\Edge |
| AVIF | 支持 | 支持 | 編解碼性能優(yōu)于 WebP,低于 JPEG XL | 有損壓縮\無(wú)損壓縮 | 直接色 | 基本一致 | 高版本 Chrome\Opera\Android\Edge |
首先,了解了解上述的一些參數(shù)含義:
- Alpha 通道:圖片是否支持透明的特性
當(dāng)然,需要指出的是,Alpha 沒(méi)有透明度的意思,不代表透明度。opacity 和 transparency 才和透明度有關(guān),前者是不透明度,后者是透明度。比如 css 中的「opacity: 0.5」就是設(shè)定元素有 50% 的不透明度。后來(lái) Alvy Ray Smith 提出每個(gè)像素再增加一個(gè) Alpha 通道,取值為0到1,用來(lái)儲(chǔ)存這個(gè)像素是否對(duì)圖片有「貢獻(xiàn)」,0代表透明、1代表不透明。也就是說(shuō),「Alpha 通道」儲(chǔ)存一個(gè)值,其外在表現(xiàn)是「透明度」,Alpha 和透明度沒(méi)啥關(guān)系
- 動(dòng)畫(huà):很好理解,圖片是否支持多幀率動(dòng)態(tài)圖片,類(lèi)似于 GIF
- 編解碼性能:圖像的解碼與編碼。這個(gè)很關(guān)鍵,很多人對(duì)待圖片容易忽視圖片的編解碼性能,解碼圖像主要從圖像文件中讀出圖像數(shù)據(jù),而編碼則是將圖像數(shù)據(jù)寫(xiě)入圖像文件。解碼與編碼的過(guò)程正好相反。而這兩者的性能耗時(shí)會(huì)影響我們頁(yè)面的的展示性能。
- 壓縮算法:該圖片格式是否支持壓縮,支持的話,圖片的壓縮又會(huì)分為無(wú)損壓縮與有損壓縮
有損壓縮算法是一種數(shù)據(jù)壓縮方法,經(jīng)過(guò)此方法壓縮、解壓的數(shù)據(jù)會(huì)與原始數(shù)據(jù)不同但是非常接近。原理是借由將次要的信息數(shù)據(jù)舍棄,犧牲一些質(zhì)量來(lái)減少數(shù)據(jù)量、提高壓縮比無(wú)損壓縮指數(shù)據(jù)經(jīng)過(guò)壓縮后,信息不受損失,還能完全恢復(fù)到壓縮前的原樣。無(wú)損壓縮通常用于嚴(yán)格要求“經(jīng)過(guò)壓縮、解壓縮的數(shù)據(jù)必須與原始數(shù)據(jù)一致”的場(chǎng)合。
- 顏色支持:會(huì)分為索引色與直接色,在過(guò)去,為了節(jié)省存儲(chǔ)空間,并非所有圖片都能支持所有顏色值,因此存在索引色這種優(yōu)化方式。
索引顏色是一種以有限的方式管理數(shù)字圖像顏色的技術(shù),以節(jié)省計(jì)算機(jī)內(nèi)存和文件存儲(chǔ),同時(shí)加速顯示刷新和文件傳輸。即用一個(gè)數(shù)字來(lái)代表(索引)一種顏色,在存儲(chǔ)圖片的時(shí)候,存儲(chǔ)一個(gè)數(shù)字的組合,同時(shí)存儲(chǔ)數(shù)字到圖片顏色的映射。這種方式只能存儲(chǔ)有限種顏色。索引色常見(jiàn)有1位(即黑白),8位(即灰階/256色),16位(即高彩),24位(即真彩),30/36/48位(即全彩)。直接色使用四個(gè)數(shù)字來(lái)代表一種顏色,這四個(gè)數(shù)字分別代表這個(gè)顏色中紅色、綠色、藍(lán)色以及透明度(即 RGBA)。現(xiàn)在流行的顯示設(shè)備可以在這四個(gè)維度分別支持256種變化,所以直接色可以表示2的32次方種顏色。
- 內(nèi)存占用:圖片對(duì)內(nèi)存資源的占用
- 兼容性:影響圖片格式能否大規(guī)模推廣的核心要素之一
WebP vs JPEG XL vs AVIF: JPEG 替代之戰(zhàn)
因?yàn)閭鹘y(tǒng)的 PNG-8/PNG-24,JPEG,GIF 各自或多或少都存在一些問(wèn)題,近些年來(lái)它們的替代方案之爭(zhēng)也愈演愈烈,核心領(lǐng)跑者可能是?WebP、JPEG XL、AVIF。
再簡(jiǎn)單了解了解它們:
WebP
WebP 最初由 Google 在 2010 年 9 月發(fā)布,其特性總結(jié)如下:
- 可以同時(shí)提供無(wú)損/有損壓縮(像 JPEG 一樣)和支持透明度(像 PNG 一樣)的圖片文件格式
- 支持動(dòng)畫(huà)效果(像 GIF 一樣)
- WebP 主要優(yōu)勢(shì)在于有損編碼,其無(wú)損編碼的性能和壓縮比表現(xiàn)一般
- WebP 的缺點(diǎn)在于其編解碼性能不是特別理想
- 在兼容性方面,除了 IE,基本已經(jīng)得到了全系列瀏覽器支持
對(duì)于復(fù)雜的圖像(比如照片)來(lái)說(shuō),WebP 無(wú)損編碼表現(xiàn)并不好,但有損編碼表現(xiàn)卻非常棒。相近質(zhì)量的圖片解碼速度 WebP 相距 JPEG 也已經(jīng)相差不大了,而文件壓縮比卻能提升不少。
下圖是我之前還在 TX 的時(shí)候做的一個(gè)測(cè)試對(duì)比:

加載同樣張數(shù)的 JPEG 與 WebP 的耗時(shí)對(duì)比:

對(duì)于 WebP 圖片格式,簡(jiǎn)單做個(gè)總結(jié):
- 目前 WebP 與 JPEG 相比較,據(jù)資料考證,編碼速度慢 10 倍,解碼速度慢 1.5 倍
- WebP 雖然會(huì)增加額外的解碼時(shí)間,但由于大幅減少了文件體積,縮短了加載的時(shí)間,大頁(yè)面圖片量較多的場(chǎng)景下,頁(yè)面的渲染速度是有較大加快的
- 目前而言,是 WebP、JPEG XL、AVIF 三者中兼容性最好的
截止至(2023-02-05)的兼容性圖:

JPEG XL
JPEG XL 由聯(lián)合圖像專(zhuān)家組(開(kāi)發(fā)原始 JPEG 標(biāo)準(zhǔn)的同一組織)于 2021 年發(fā)布,旨在成為傳統(tǒng) JPEG 的長(zhǎng)期替代品。作為一種免版稅的開(kāi)源標(biāo)準(zhǔn),JPEG XL 的創(chuàng)建者希望其格式的開(kāi)放性能夠吸引網(wǎng)絡(luò)開(kāi)發(fā)人員采用該標(biāo)準(zhǔn),該格式的擴(kuò)展名為?.jxl,JXL 核心比特流于 2021 年 1 月凍結(jié),文件格式于 2021 年 4 月定稿。:
JPEG XL 中的 X 指 2000 年以來(lái)的多個(gè) JPEG 標(biāo)準(zhǔn)的名稱(chēng):JPEG XT[1]、JPEG XR[2]、JPEG XS[3],而 L 表示 'long-term',表示“長(zhǎng)期”。創(chuàng)建這種格式是為替換舊的JPEG文件格式[4],并使用足夠長(zhǎng)的時(shí)間。
其主要特點(diǎn)有:
- 與傳統(tǒng)圖像格式(例如JPEG、GIF和PNG)相比,有著更佳的效率與更豐富的功能
- 全面支持廣色域和 HDR,支持 Alpha 通道,支持多幀(也就是動(dòng)畫(huà)支持)
- 有損壓縮時(shí):相同的視覺(jué)質(zhì)量,比 JPEG 小約 60%。
- 無(wú)損壓縮時(shí):比 PNG 減小約 35%(對(duì)于 HDR,減小 50%)。
- 支持無(wú)損 JPEG 轉(zhuǎn)碼,減小約 20% 文件大小。
- 漸進(jìn)式解碼,專(zhuān)為支持不同顯示分辨率的響應(yīng)式加載
- 開(kāi)源免費(fèi):具有使用三條款版BSD許可證[5]的開(kāi)源[6]參考實(shí)現(xiàn)的免版稅格式
看看同一張圖片,相同質(zhì)量下的大小表現(xiàn):

數(shù)據(jù)來(lái)源:技術(shù)周刊 2021-04-15:2021最值得期待的新技術(shù) JPEG XL[7]
JPEG XL 是目前而言,最有可能全面替代傳統(tǒng)圖片格式(Gif、PNG、JPEG)的下一代標(biāo)準(zhǔn),當(dāng)然,在今天,需要看看其兼容性:

好吧,目前的兼容有點(diǎn)離譜。Chrome 從 91 版本開(kāi)始已經(jīng)實(shí)驗(yàn)室性質(zhì)支持了?.jxl?格式的圖片,需要通過(guò)?--enable-features=JXL?配置開(kāi)啟。遺憾的是,從 Chrome 110 開(kāi)始,Chrome 又不再支持 JPEG?XL?。?
有趣的是,Chrome 從 110 版本開(kāi)始中棄用了對(duì) JPEG-XL 的支持,谷歌的回答是,人們對(duì) JPEG-XL 沒(méi)有足夠的興趣,并且與現(xiàn)有格式相比也沒(méi)有足夠的優(yōu)勢(shì)。谷歌之前一直對(duì) JPEG 的支持都是實(shí)驗(yàn)性的性質(zhì)的,他們認(rèn)為 JPEG XL 缺乏生態(tài)系統(tǒng)支持,并且該格式?jīng)]有足夠多的好處(相對(duì) WebP 和 AVIF)。也就是說(shuō),目前而言,Chrome 對(duì) WebP 和 AVIF 等替代格式更感興趣。
AVIF
最后,我們?cè)賮?lái)看看 AVIF 格式圖片。
AVIF 是由開(kāi)放媒體聯(lián)盟 (AOM) 開(kāi)發(fā)并于 2019 年發(fā)布的另一種最新圖像格式。該格式基于 AV1 視頻編解碼器,源自視頻幀。其特點(diǎn)如下:
- 同樣的,與傳統(tǒng)圖像格式(例如JPEG、GIF和PNG)相比,有著更佳的效率與更豐富的功能
- 支持 Alpha 通道,支持動(dòng)態(tài)圖像[8]和動(dòng)畫(huà)[9]
- 支持有損、無(wú)損壓縮。AVIF 文件在低保真有損圖像壓縮方面表現(xiàn)出色(比 JPEG XL 更優(yōu))。壓縮的 AVIF 圖像保留了很高的圖片質(zhì)量,避免了惱人的壓縮偽影等問(wèn)題
- 相對(duì)而言,AVIF 的解碼和編碼速度不如 JPEG XL,它不支持漸進(jìn)式渲染
- 最后,再看看兼容性,目前(2023-02-05)它的兼容性介于 WebP 與 JPEG XL 之間
看看 CaniUse 的數(shù)據(jù):

下圖是 WebP vs JPEG XL vs AVIF 三者在圖片解碼上的性能表現(xiàn):

圖片來(lái)源于:Encode.su -- JPEG XL vs. AVIF[10]
從圖中可以看到,對(duì)于解碼性能的對(duì)比,結(jié)果居然是 WebP > AVIF > JPEG XL 。JPEG XL 的編解碼性能并沒(méi)有其描述的那么強(qiáng)大。
圖片格式總結(jié)
總結(jié)一下,WebP、AVIF 和 JPEG XL 都是瀏覽器不廣泛支持的新型圖像格式。雖然 WebP、AVIF 已經(jīng)存在很長(zhǎng)時(shí)間,但到今天,影響它們大規(guī)模使用的依舊是兼容問(wèn)題。
JPEG XL、AVIF、Web 各自有各自的特點(diǎn)與優(yōu)勢(shì),并且都未完全得到任何瀏覽器的支持。
雖然 AVIF、JPEG XL 等新型圖片格式未得到任何瀏覽器的完全支持,但是在新版本的 Chrome、Firefox 和 Edge Chromium,可以使用配置標(biāo)志啟用對(duì)應(yīng)圖像格式,配合 HTML 的 Picture 標(biāo)簽,我們還是可以一定程度上對(duì)我們的圖片進(jìn)行格式選擇上的優(yōu)化的。
這,就可以引出我們要說(shuō)的第二部分 -- HTML Picture 標(biāo)簽的使用。
Picture 元素的使用
HTML5 規(guī)范新增了 Picture Element。那么?<picture>?元素的作用是什么呢?
<picture>?元素通過(guò)包含零或多個(gè)?<source>?元素和一個(gè)?<img>?元素來(lái)為不同的顯示/設(shè)備場(chǎng)景提供圖像版本。瀏覽器會(huì)選擇最匹配的子?<source>?元素,如果沒(méi)有匹配的,就選擇?<img>?元素的 src 屬性中的 URL。然后,所選圖像呈現(xiàn)在?<img>?元素占據(jù)的空間中。
什么意思呢?怎么使用?<picture>?元素呢?
假設(shè),沒(méi)有?<picture>?,只有?<img>?元素,我們想盡可能在支持一些現(xiàn)代圖片格式的瀏覽器上使用類(lèi)似于上述我們提到的 WebP、AVIF 和 JPEG XL ?等圖片格式,而不支持的瀏覽器回退使用常規(guī)的 JPEG、PNG 等。沒(méi)錯(cuò),就是一種漸進(jìn)增強(qiáng)的思想,該怎么辦呢?
只能是 JavaScript 去寫(xiě)對(duì)應(yīng)的邏輯,通過(guò) JS 腳本進(jìn)行特性查詢(xún),動(dòng)態(tài)賦值給?<img>?的 src。
我們來(lái)看看對(duì)應(yīng)的語(yǔ)法:
<picture>
??<!--?可能是一些對(duì)兼容性有要求的,但是性能表現(xiàn)更好的現(xiàn)代圖片格式-->
??<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>?
而有了?<picture>?后,瀏覽器將原生支持上述的一些列操作,簡(jiǎn)而言之,<picture>?元素的作用:
-
通過(guò)?
<source>?給出一系列對(duì)兼容性有所要求的現(xiàn)代圖片格式選項(xiàng) -
通過(guò)?
<img>?給出兜底的高兼容性圖片格式選項(xiàng) -
瀏覽器通過(guò)對(duì)給出的圖片格式做特性檢測(cè),要決定加載哪個(gè) URL,user agent 檢查每個(gè)?
<source>?的 srcset、media 和 type 屬性,來(lái)選擇最匹配頁(yè)面當(dāng)前布局、顯示設(shè)備特征等的兼容圖像。 -
最終,所選圖像呈現(xiàn)在?
<img>?元素占據(jù)的空間中
本章總結(jié)
總結(jié)一下,本文對(duì)常見(jiàn)的圖片格式以及最新的幾種未被大規(guī)模兼容的圖片格式進(jìn)行的對(duì)比,它們分別是:
- PNG-8/PNG-24
- JPEG
- GIF
- WebP
- JPEG XL
- AVIF
其后,著重介紹了 3 種現(xiàn)代圖片格式:WebP、JPEG XL、AVIF。相對(duì)于 JPEG 等傳統(tǒng)格式,它們?cè)谏时憩F(xiàn)、動(dòng)畫(huà)支持、是否支持無(wú)損有損壓縮、壓損比率、編解碼性能上有著更進(jìn)一步的提升,正在成為下一階段 Web 圖像的標(biāo)準(zhǔn)。
最后,介紹了?<picture>?元素,借助它,我們能更好的實(shí)現(xiàn)圖片的漸進(jìn)增強(qiáng)。
適配不同的屏幕尺寸及 DPR
第二個(gè)模塊,我們來(lái)看看圖片資源如何更好的適配不同的屏幕尺寸。
這里首先會(huì)涉及一個(gè)預(yù)備知識(shí),屏幕的 DPR 值,那么,什么是 DPR 呢?要了解 DPR,又需要知道什么是設(shè)備獨(dú)立像素?以及?物理像素。
設(shè)備獨(dú)立像素
以 iPhone6/7/8為例,這里我們打開(kāi) Chrome 開(kāi)發(fā)者工具:

這里的?375 * 667?表示的是什么呢,表示的是設(shè)備獨(dú)立像素(DIP),也可以理解為 CSS 像素,也稱(chēng)為邏輯像素:
設(shè)備獨(dú)立像素 = CSS 像素 = 邏輯像素
如何記憶呢?這里使用 CSS 像素來(lái)記憶,也就是說(shuō)。我們?cè)O(shè)定一個(gè)寬度為 375px 的 div,剛好可以充滿(mǎn)這個(gè)設(shè)備的一行,配合高度 667px ,則 div 的大小剛好可以充滿(mǎn)整個(gè)屏幕。
物理像素
OK,那么,什么又是物理像素呢。我們到電商網(wǎng)站購(gòu)買(mǎi)手機(jī),都會(huì)看一看手機(jī)的參數(shù),以 JD 上的 iPhone7 為例:

可以看到,iPhone7 的分辨率是?1334 x 750,這里描述的就是屏幕實(shí)際的物理像素。
物理像素,又稱(chēng)為設(shè)備像素。顯示屏是由一個(gè)個(gè)物理像素點(diǎn)組成的,1334 x 750?表示手機(jī)分別在垂直和水平上所具有的像素點(diǎn)數(shù)。通過(guò)控制每個(gè)像素點(diǎn)的顏色,就可以使屏幕顯示出不同的圖像,屏幕從工廠出來(lái)那天起,它上面的物理像素點(diǎn)就固定不變了,單位為pt。
設(shè)備像素 = 物理像素
DPR(Device Pixel Ratio) 設(shè)備像素比
OK,有了上面兩個(gè)概念,就可以順理成章引出下一個(gè)概念。DPR(Device Pixel Ratio) 設(shè)備像素比,這個(gè)與我們通常說(shuō)的視網(wǎng)膜屏(多倍屏,Retina屏)有關(guān)。
設(shè)備像素比描述的是未縮放狀態(tài)下,物理像素和設(shè)備獨(dú)立像素的初始比例關(guān)系。
簡(jiǎn)單的計(jì)算公式:
DPR = 物理像素 / 設(shè)備獨(dú)立像素
我們套用一下上面 iPhone7 的數(shù)據(jù)(取設(shè)備的物理像素寬度與設(shè)備獨(dú)立像素寬度進(jìn)行計(jì)算):
iPhone7’s DPR = iPhone7’s 物理像素寬度 / iPhone7's 設(shè)備獨(dú)立像素寬度 = 2
750 / 375 = 2 或者是 1334 / 667 = 2
可以得到 iPhone7 的 dpr 為 2。也就是我們常說(shuō)的視網(wǎng)膜屏幕。
視網(wǎng)膜(Retina)屏幕是蘋(píng)果公司"發(fā)明"的一個(gè)營(yíng)銷(xiāo)術(shù)語(yǔ)。蘋(píng)果公司將?dpr > 1?的屏幕稱(chēng)為視網(wǎng)膜屏幕。

在視網(wǎng)膜屏幕中,以 dpr = 2 為例,把 4(2x2) 個(gè)像素當(dāng) 1 個(gè)像素使用,這樣讓屏幕看起來(lái)更精致,但是元素的大小本身卻不會(huì)改變:

OK,我們?cè)賮?lái)看看 iPhone XS Max:

它的物理像素如上圖是?2688 x 1242,

它的 CSS 像素是?896 x 414,很容易得出 iPhone XS Max 的 dpr 為 3。
為不同 DPR 屏幕,提供恰當(dāng)?shù)膱D片
那么,DPR 和圖片適配有什么關(guān)系呢?
舉個(gè)例子,同樣的 CSS 像素大小下,屏幕如果有不同 DPR,同樣大小的圖片渲染出來(lái)的效果不盡相同。
我們以?dpr = 3?的手機(jī)為例子,在?300 x 389?CSS 像素大小的范圍內(nèi),渲染?1倍/2倍/3倍?圖的效果如下:

實(shí)際圖片所占的物理像素為 900 x 1167。
可以看到,在高 DPR 設(shè)備下提供只有 CSS 像素大小的圖片,是非常模糊的。
因此,為了在不同的 DPR 屏幕下,讓圖片看起來(lái)都不失真,我們需要為不同 DPR 的圖片,提供不同大小的圖片。
那么,有哪些可行的解決方案呢?
方案一:無(wú)腦多倍圖
假設(shè),在移動(dòng)端假設(shè)我們需要一張 CSS 像素為?300 x 200?的圖像,考慮到現(xiàn)在已經(jīng)有了 dpr = 3 的設(shè)備,那么要保證圖片在 dpr = 3 的設(shè)備下也正常高清展示,我們最大可能需要一張?900 x 600?的原圖。
這樣,不管設(shè)備的 dpr 是否為 3,我們統(tǒng)一都使用 3 倍圖。這樣即使在 dpr = 1,dpr = 2 的設(shè)備上,也能非常好的展示圖片。
當(dāng)然這樣并不可取,會(huì)造成大量帶寬的浪費(fèi)。
現(xiàn)代瀏覽器,提供了更好的方式,讓我們能夠根據(jù)設(shè)備 dpr 的不同,提供不同尺寸的圖片。
方案二:媒體查詢(xún)
方案二,我們可以考慮使用媒體查詢(xún)。到今天,我們可以通過(guò)相應(yīng)的媒體查詢(xún),得知當(dāng)前的設(shè)備的 DPR 值,這樣,我們就可以在對(duì)應(yīng)的媒體查詢(xún)中,使用對(duì)應(yīng)的圖片。
像是這樣:
#id?{?
????background:?url(xxx@2x.png)?
}
@media?(device-pixel-ratio:?2)?{
????#id?{?
????????background:?url(xxx@2x.png)?
????}
}
@media?(device-pixel-ratio:?3)?{
????#id?{?
????????background:?url(xxx@3x.png)?
????}
}
這個(gè)方案的缺點(diǎn)在于:
- 要寫(xiě)的代碼可能太多了,而且,可能存在一些介于 1~2,2~3 之間的 DPR 值,不好窮舉出所有場(chǎng)景
-
需要注意語(yǔ)法需要的兼容性,需要添加前綴,譬如?
-webkit-min-device-pixel-ratio,當(dāng)然這個(gè)可以由?autoprefixer?輔助解決
方案三:CSS 配合 image-set 語(yǔ)法
image-set?屬于 CSS background 中的一種語(yǔ)法,image-set()?函數(shù)為設(shè)備提供最合適的圖像分辨率,它提供一組圖像選項(xiàng),每個(gè)選項(xiàng)都有一個(gè)相關(guān)的 DPR 聲明,瀏覽器將從中選擇最適合設(shè)備的圖像進(jìn)行設(shè)置。
什么意思呢,來(lái)看看代碼:
.img?{
????/*?不支持?image-set?的瀏覽器*/
????background-image:?url('../[email protected]');
????/*?支持?image-set?的瀏覽器*/
????background-image:?image-set(
????????url('./[email protected]')?2x,
????????url('./[email protected]')?3x
????);
}
這樣一看,作用應(yīng)該很清晰了。對(duì)于支持?image-set?語(yǔ)法的瀏覽器:
-
如果其設(shè)備對(duì)應(yīng)的 DPR 為 2,會(huì)選取這條?
url('./[email protected]') 2x?記錄,也就是最終生效的 URL 是?'./[email protected]'; -
如果其設(shè)備對(duì)應(yīng)的 DPR 為 3,會(huì)選取這條?
url('./[email protected]') 3x?記錄,也就是最終生效的 URL 是?'./[email protected]';
其中的?2x,3x?就是用于匹配 DRP的。
使用?image-set?的一些痛點(diǎn)與媒體查詢(xún)方案類(lèi)似。代碼量與兼容性語(yǔ)法,而且難以匹配所有情況。
方案四:srcset 配合 1x 2x 像素密度描述符
簡(jiǎn)單來(lái)說(shuō),srcset 可以根據(jù)不同的 dpr 拉取對(duì)應(yīng)尺寸的圖片:
<div?class='illustration'>
???<img?src='illustration-small.png'
???????srcset='images/illustration-small.png?1x,
???????????????images/illustration-big.png?2x'
???>
</div>
上面?srcset?里的 1x,2x 表示?像素密度描述符,表示
-
當(dāng)屏幕的 dpr = 1 時(shí),使用?
images/illustration-small.png?這張圖 -
當(dāng)屏幕的 dpr = 2 時(shí),使用?
images/illustration-big.png?這張圖 -
如果不支持?
srcset?語(yǔ)法,src='illustration-small.png'?將會(huì)是最終的兜底方案
方案五:srcset 屬性配合 sizes 屬性 w 寬度描述符
上面 1x,2x 的寫(xiě)法比較容易接受易于理解。
但是,上述 3 種方案都存在統(tǒng)一的問(wèn)題,只考慮了 DPR,但是忽略了響應(yīng)性布局的復(fù)雜性與屏幕的多樣性。
因此,規(guī)范還推出了一種方案 --?srcset 屬性配合 sizes 屬性 w 寬度描述符。
srcset?屬性還有一個(gè) w 寬度描述符,配合?sizes?屬性一起使用,可以覆蓋更多的面。
sizes?屬性怎么理解呢?它定義圖像元素在不同的視口寬度時(shí),可能的大小值。
以下面這段代碼為例子:
<img?
????????sizes?=?“(min-width:?600px)?600px,?300px"?
????????src?=?"photo.png"?
????????srcset?=?“[email protected]?300w,
???????????????????????photo@2x.png?600w,
???????????????????????photo@3x.png?1200w,
>
解析一下:
sizes = “(min-width: 600px) 600px, 300px"?的意思是:
- 如果屏幕當(dāng)前的 CSS 像素寬度大于或者等于 600px,則圖片的 CSS 寬度為 600px
- 反之,則圖片的 CSS 寬度為 300px
也就是 sizes 屬性聲明了在不同寬度下圖片的 CSS 寬度表現(xiàn)。這里可以理解為,大屏幕下圖片寬度為 600px,小屏幕下圖片寬度為 300px。
需要注意的是,這里大屏、小屏下圖片具體的寬度表現(xiàn),還是需要借助媒體查詢(xún)代碼,經(jīng)由 CSS 實(shí)現(xiàn)的
srcset = “[email protected] 300w, [email protected] 600w, [email protected] 1200w?里面的 300w,600w,900w 叫寬度描述符。
那么,怎么確定當(dāng)前場(chǎng)景會(huì)選取哪張圖片呢?
當(dāng)前屏幕 dpr = 2 ,CSS 寬度為 375px。
當(dāng)前屏幕 CSS 寬度為 375px,則圖片 CSS 寬度為 300px。分別用上述 3 個(gè)寬度描述符的數(shù)值除以 300。
- 300 / 300 = 1
- 600 / 300 = 2
- 1200 / 300 = 4
上面計(jì)算得到的 1、 2、 4 即是算出的有效的像素密度,換算成和 x 描述符等價(jià)的值 。這里 600w 算出的 2 即滿(mǎn)足 dpr = 2 的情況,選擇此張圖。
當(dāng)前屏幕 dpr = 3 ,CSS 寬度為 414px。
當(dāng)前屏幕 CSS 寬度為 414px,則圖片 CSS 寬度仍為 300px。再計(jì)算一次:
- 300 / 300 = 1
- 600 / 300 = 2
- 1200 / 300 = 4
因?yàn)?dpr = 3,2 已經(jīng)不滿(mǎn)足了,則此時(shí)會(huì)選擇 1200w 這張圖。
當(dāng)前屏幕 dpr = 1 ,CSS 寬度為 1920px。
當(dāng)前屏幕 CSS 寬度為 1920px,則圖片 CSS 寬度變?yōu)榱?600px。再計(jì)算一次:
- 300 / 600 = .5
- 600 / 600 = 1
- 1200 / 600 = 2
因?yàn)?dpr = 1,所以此時(shí)會(huì)選擇 600w 對(duì)應(yīng)的圖片。
具體的可以試下這個(gè) Demo:CodePen Demo -- srcset屬性配合w寬度描述符配合sizes屬性[11]
此方案的意義在于考慮到了響應(yīng)性布局的復(fù)雜性與屏幕的多樣性,利用上述規(guī)則,可以一次適配 PC 端大屏幕和移動(dòng)端高清屏,一箭多雕。
嗯,總結(jié)一下,在實(shí)現(xiàn)響應(yīng)式圖像時(shí),我們同時(shí)使用?srcset?和?sizes?屬性。它們的作用是:
-
srcset:定義多個(gè)不同寬度的圖像源,讓瀏覽器在 HTML 解析期間選擇最合適的圖像源 -
sizes:定義圖像元素在不同的視口寬度時(shí),可能的大小值
有了這些屬性后,瀏覽器就會(huì)根據(jù) srcset/size 來(lái)創(chuàng)建一個(gè)分辨率切換器的響應(yīng)式圖片,可以在不同的分辨率的情況下,提供相同尺寸的圖像,或者在不同的視圖大小的情況下,提供不同尺寸大小的圖像。
本章總結(jié)
本章節(jié)一共列舉了 5 種實(shí)現(xiàn)響應(yīng)式圖片,適配不同屏幕大小,不同 DPR 的方式,它們分別是:
- 無(wú)腦多倍圖的方式
- DRP 媒體查詢(xún)
-
CSS Background 中的使用?
image-set - srcset 配合 1x 2x 像素密度描述符
- srcset 屬性配合 sizes 屬性 w 寬度描述符
合理使用它們,可以有效的為不同屏幕,提供最為恰當(dāng)?shù)膱D片資源,在保證用戶(hù)體驗(yàn)的同時(shí),盡可能節(jié)省帶寬。
它們各有優(yōu)缺點(diǎn),可以根據(jù)自己實(shí)際的業(yè)務(wù)場(chǎng)景,選取合適相對(duì)成本最低的方案,并且適當(dāng)?shù)呐浜?Autoprefixer 以及一些 PostCSS 等工具,簡(jiǎn)化代碼量。
圖片的寬高比、裁剪與縮放
OK,下面進(jìn)入到我們的第三個(gè)模塊,圖片的寬高比、裁剪與縮放。我們會(huì)介紹 4 個(gè)新的特性:
-
aspect-ratio -
object-fit -
object-position -
image-rendering
使用?aspect-ratio?避免布局偏移
很多時(shí)候,只能使用固定尺寸大小的圖片,我們的布局可能是這樣:

對(duì)應(yīng)的布局:
<ul?class="g-container">
????<li>
????????<img?src="http://placehold.it/150x100">
????????<p>圖片描述</p>
????</li>
</ul>
ul?li?img?{
????width:?150px;
}
當(dāng)然,萬(wàn)一假設(shè)后端接口出現(xiàn)一張非正常大小的圖片,上述不加保護(hù)的布局就會(huì)出問(wèn)題:

所以對(duì)于圖片,我們總是建議同時(shí)寫(xiě)上高和寬,避免因?yàn)閳D片尺寸錯(cuò)誤帶來(lái)的布局問(wèn)題:
ul?li?img?{
????width:?150px;
????height:?100px;
}
同時(shí),給?<img>?標(biāo)簽同時(shí)寫(xiě)上高寬,可以在圖片未加載之前提前占住位置,避免圖片從未加載狀態(tài)到渲染完成狀態(tài)高寬變化引起的重排問(wèn)題。
當(dāng)然,到今天,我們還可以使用?aspect-ratio?設(shè)定圖片的高寬比。
aspect-ratio?CSS 屬性為容器規(guī)定了一個(gè)期待的寬高比,這個(gè)寬高比可以用來(lái)計(jì)算自動(dòng)尺寸以及為其他布局函數(shù)服務(wù)。
像是上面的代碼,我們就可以替換成:
ul?li?img?{
????width:?150px;
????aspect-ratio:?3?/?2;
}
當(dāng)然,有的時(shí)候,我們的布局是響應(yīng)式動(dòng)態(tài)在變化的,容器的寬度也是不確定的,因此,有了?aspect-ratio?之后,我們的寫(xiě)法就可以更佳的舒服。
ul?li?img?{
????width:?100%;
????aspect-ratio:?3?/?2;
}
這里,容器基于 Flex 彈性布局或者響應(yīng)式布局,其寬度是不固定的,但是圖片的寬高比是固定的,使用?aspect-ratio: 3 / 2?就能非常好的適配這種情況。
我們借助了 aspect-ratio 這個(gè) CSS 中較新的屬性來(lái)始終自動(dòng)獲得正確的寬高比,無(wú)論其父元素的寬度如何變化。
當(dāng)然,
aspect-ratio?不僅僅只是能運(yùn)用在這里,在?aspect-ratio?出現(xiàn)之前,我們只能通過(guò)一些其它的 Hack 方式,譬如設(shè)置?padding-top?等方式模擬固定的寬高比。在?aspect-ratio?之后,我們終于有了設(shè)定容器固定寬高比的能力。
object-fit?避免圖片拉伸
當(dāng)然,限制高寬也會(huì)出現(xiàn)問(wèn)題,譬如圖片被拉伸了,非常的難看:

這個(gè)時(shí)候,我們可以借助?object-fit,它能夠指定可替換元素的內(nèi)容(也就是圖片)該如何適應(yīng)它的父容器的高寬。
ul?li?img?{
????width:?150px;
????aspect-ratio:?3?/?2;
????object-fit:?cover;
}
利用?object-fit: cover,使圖片內(nèi)容在保持其寬高比的同時(shí)填充元素的整個(gè)內(nèi)容框。

object-fit?的取值有?fill、none、contain、cover,與?background-size?類(lèi)似,可以類(lèi)比記憶。
也可以看看這張圖,很易于理解:

object-fit?還有一個(gè)配套屬性?object-position,它可以控制圖片在其內(nèi)容框中的位置。(類(lèi)似于?background-position),默認(rèn)是?object-position: 50% 50%,如果你不希望圖片居中展示,可以使用它去改變圖片實(shí)際展示的 position。
ul?li?img?{
????width:?150px;
????aspect-ratio:?3?/?2;
????object-fit:?cover;
????object-position:?50%?100%;
}

像是這樣,object-position: 100% 50%?指明從底部開(kāi)始展示圖片。這里有一個(gè)很好的 Demo 可以幫助你理解?object-position。
CodePen Demo -- Object position [13]
使用?image-rendering?設(shè)置圖片縮放算法
相對(duì)于上面幾個(gè)新特性,image-rendering?會(huì)更為冷門(mén)。
很多時(shí)候,我們?cè)O(shè)置一個(gè)圖片在頁(yè)面上的展示大小為?200px x 200px,但是圖片的原始尺寸可能是?800px x 800px,也可能是?50px x 50px。
這個(gè)時(shí)候,我們就可以利用?image-rendering,設(shè)置圖片在縮放狀態(tài)下的展示算法。
image-rendering?在特定的場(chǎng)景下,能夠起到奇效。
來(lái)看這樣一個(gè)有意思的 DEMO,假設(shè)我們有這樣一個(gè)原圖效果,它是一個(gè)二維碼,大小為?100px x 100px:
如果我們將它放大,放到很大,明顯,這個(gè)二維碼會(huì)失真,像是這樣:
OK,在這種放大失真的情況想,可以使用?image-rendering?改變圖片縮放算法,這里我們?cè)囈幌?image-rendering: pixelated:
.img?{
??image-rendering:?pixelated;
}
效果變化,如下圖所示:
可以看到,image-rendering: pixelated?處理過(guò)的圖像,竟然變得如此清晰!
CodePen Demo -- QrCode Image-rendering demo [14]
來(lái)看看?image-rendering?的幾個(gè)取值:
-
image-rendering: auto:自 Gecko 1.9(Firefox 3.0)起,Gecko 使用雙線性(bilinear)算法進(jìn)行重新采樣(高質(zhì)量)。 -
image-rendering: smooth:使用能最大化圖像客觀觀感的算法來(lái)縮放圖像 -
image-rendering: high-quality:與 smooth 相同,但更傾向于高質(zhì)量的縮放。 -
image-rendering: crisp-edges:必須使用可有效保留對(duì)比度和圖像中的邊緣的算法來(lái)對(duì)圖像進(jìn)行縮放,并且,該算法既不會(huì)平滑顏色,又不會(huì)在處理過(guò)程中為圖像引入模糊。合適的算法包括最近鄰居(nearest-neighbor)算法和其他非平滑縮放算法,比如 2×SaI 和 hqx-* 系列算法。此屬性值適用于像素藝術(shù)作品,例如一些網(wǎng)頁(yè)游戲中的圖像。 -
image-rendering: pixelated:放大圖像時(shí),使用最近鄰居算法,因此,圖像看著像是由大塊像素組成的。縮小圖像時(shí),算法與 auto 相同。
雖然規(guī)范定義了挺多值,但是實(shí)際上,現(xiàn)代瀏覽器基本暫時(shí)只支持:auto、pixelated、以及?-webkit-optimize-contrast(Chrome 下的 smooth)。
看描述都會(huì)挺懵逼的,實(shí)際使用的時(shí)候,最好每個(gè)都試一下驗(yàn)證一下效果。總結(jié)而言,image-rendering?的作用是在圖像縮放時(shí),提供不一樣的渲染方式,讓圖片的展示形態(tài)更為多樣化,或者說(shuō)是盡可能的去減少圖片的失真帶來(lái)的信息損耗。
我們?cè)倏匆粋€(gè) DEMO,原圖如下(例子來(lái)源于 W3C 規(guī)范文檔):

實(shí)際效果:

當(dāng)然,看上去?pixelated?的效果挺好,這是由于這是一張偏向于矢量的圖片,細(xì)節(jié)不多,對(duì)于高精度的人物圖,就不太適用于?pixelated,容易把圖片馬賽克化。
真正規(guī)范希望的在放大后讓圖片盡可能不失真的?crisp-edges?效果,目前暫時(shí)沒(méi)有得到瀏覽器的實(shí)現(xiàn)。后面可以期待一下。
CodePen Demo -- Image-rendering demo [15]
本章總結(jié)
這一章,我們介紹了 4 個(gè)較新的 CSS 特性:
-
aspect-ratio:控制容器的寬高比,避免產(chǎn)生布局偏移及抖動(dòng) -
object-fit:設(shè)定內(nèi)容應(yīng)該如何適應(yīng)到其使用高度和寬度確定的框,避免圖片拉伸 -
object-position:基于?object-fit,設(shè)置圖片實(shí)際展示的 position 范圍 -
image-rendering:控制圖片在縮放狀態(tài)下的展示算法
合理利用它們,可以給用戶(hù)在圖片上以更好的體驗(yàn)。
懶加載/異步圖像解碼方案
繼續(xù)第四章。本章節(jié),我們來(lái)討論下圖片的懶加載與異步圖像解碼方案。
圖片的懶加載
懶加載是一種網(wǎng)頁(yè)性能優(yōu)化的常見(jiàn)方式,它能極大的提升用戶(hù)體驗(yàn)。到今天,現(xiàn)在一張圖片超過(guò)幾 M 已經(jīng)是常見(jiàn)事了。如果每次進(jìn)入頁(yè)面都需要請(qǐng)求頁(yè)面上的所有的圖片資源,會(huì)較大的影響用戶(hù)體驗(yàn),對(duì)用戶(hù)的帶寬也是一種極大的損耗。
所以,圖片懶加載的意義即是,當(dāng)頁(yè)面未滾動(dòng)到相應(yīng)區(qū)域,該區(qū)域內(nèi)的圖片資源(網(wǎng)絡(luò)請(qǐng)求)不會(huì)被加載。反之,當(dāng)頁(yè)面滾動(dòng)到相應(yīng)區(qū)域,相關(guān)圖片資源的請(qǐng)求才會(huì)被發(fā)起。
在過(guò)去,我們通常都是使用 JavaScript 方案進(jìn)行圖片的懶加載。而今天,我們?cè)趫D片的懶加載實(shí)現(xiàn)上,有了更多不一樣的選擇。
JavaScript 方案實(shí)現(xiàn)圖片的懶加載
首先,回顧一下過(guò)往最常見(jiàn)的,使用 JavaScript 方案實(shí)現(xiàn)圖片的懶加載。
通過(guò) JavaScript 實(shí)現(xiàn)的懶加載,主要是兩種方式:
-
監(jiān)聽(tīng) onscroll 事件,通過(guò)?
getBoundingClientRect?API 獲取元素圖片距離視口頂部的距離,配合當(dāng)前可視區(qū)域的位置實(shí)現(xiàn)圖片的懶加載 -
通過(guò) HTML5 的?
IntersectionObserver?API,Intersection Observer(交叉觀察器)[4]?配合監(jiān)聽(tīng)元素的?isIntersecting?屬性,判斷元素是否在可視區(qū)內(nèi),能夠?qū)崿F(xiàn)比監(jiān)聽(tīng) onscroll 性能更佳的圖片懶加載方案
但是,JavaScript 方案的一個(gè)劣勢(shì)在于,不管如何,需要引入一定量的 JavaScript 代碼,進(jìn)行一定量的運(yùn)算。
到今天,其實(shí)我們有更多的其他便捷的方式去實(shí)現(xiàn)圖片的懶加載。
使用?content-visibility: auto?實(shí)現(xiàn)圖片內(nèi)容的延遲渲染
首先,介紹一個(gè)非常有用,但是相對(duì)較為冷門(mén)的屬性 --?content-visibility。
content-visibility:屬性控制一個(gè)元素是否渲染其內(nèi)容,它允許用戶(hù)代理(瀏覽器)潛在地省略大量布局和渲染工作,直到需要它為止。
利用?content-visibility?的特性,我們可以實(shí)現(xiàn)如果該元素當(dāng)前不在屏幕上,則不會(huì)渲染其后代元素。
假設(shè)我們有這樣一個(gè) DEMO:
<div?class="g-wrap">
????//?模塊?1
????<div?class="paragraph">
????????<p>Lorem?Start!</p>???
????????<img?src="https://s1.ax1x.com/2023/02/20/pSX1xMV.png"?alt=""?/>
????????<p>Lorem?End!</p>??
????</div>
????//?模塊?2
????<div?class="paragraph">
????????<p>Lorem?Start!</p>???
????????<img?src="https://s1.ax1x.com/2023/02/20/pSX1xMV.png"?alt=""?/>
????????<p>Lorem?End!</p>??
????</div>
????//?...?連續(xù)幾十個(gè)上述類(lèi)似的結(jié)構(gòu)
</div>
只需要給需要延遲(實(shí)時(shí))渲染的元素,設(shè)置簡(jiǎn)單的 CSS 樣式:
.paragraph?{
????content-visibility:?auto;
}
我們來(lái)看一下,設(shè)置了?content-visibility: auto?與沒(méi)設(shè)置的區(qū)別。
如果,不添加上述的?content-visibility: auto?代碼,頁(yè)面的滾動(dòng)條及滾動(dòng)效果如下:

那么,在添加了?content-visibility: auto?之后,注意觀察頁(yè)面的滾動(dòng)條及滾動(dòng)效果:

可以看到滾動(dòng)條在向下滾動(dòng)在不斷的抽搐,這是由于下面不在可視區(qū)域內(nèi)的內(nèi)容,一開(kāi)始是沒(méi)有被渲染的,在每次滾動(dòng)的過(guò)程中,才逐漸渲染,以此來(lái)提升性能。
Codepen Deom -- content-visibility: auto Image Load Demo [17]
content-visibility: auto?VS 圖片懶加載
當(dāng)然,其實(shí)使用?content-visibility: auto?并不能真正意義上實(shí)現(xiàn)圖片的懶加載。
這是因?yàn)椋幢惝?dāng)前頁(yè)面可視區(qū)域外的內(nèi)容未被渲染,但是圖片資源的 HTTP/HTTPS 請(qǐng)求,依然會(huì)在頁(yè)面一開(kāi)始被觸發(fā)!
因此,這也得到了一個(gè)非常重要的結(jié)論:
content-visibility: auto?無(wú)法直接替代圖片懶加載,設(shè)置了?content-visibility: auto?的元素在可視區(qū)外只是未被渲染,但是其中的靜態(tài)資源仍舊會(huì)在頁(yè)面初始化的時(shí)候被全部加載。因此,它更像是一個(gè)虛擬列表的替代方案。
關(guān)于?
content-visibility?本文限于篇幅,沒(méi)有完全展開(kāi),但是它是一個(gè)非常有意思且對(duì)渲染性能有幫助的屬性,完整的教程,你可以看我的這篇文章 --?使用 content-visibility 優(yōu)化渲染性能[18]
使用?loading=lazy?HTML 屬性實(shí)現(xiàn)圖片懶加載
OK,content-visibility?很不錯(cuò),但是略有瑕疵。但是,我們還有其他方式。
HTML5 新增了一個(gè)?loading?屬性。
到今天,除了 IE 系列瀏覽器,目前都支持通過(guò)?loading?屬性實(shí)現(xiàn)延遲加載。此屬性可以添加到?<img>?元素中,也可以添加到?<iframe>?元素中。
屬性的值為?loading=lazy?會(huì)告訴瀏覽器,如果圖像位于可視區(qū)時(shí),則立即加載圖像,并在用戶(hù)滾動(dòng)到它們附近時(shí)獲取其他圖像。
我們可以像是這樣使用它:
<img?src="xxx.png"?loading="lazy">
這樣,便可以非常便捷的實(shí)現(xiàn)圖片的懶加載,省去了添加繁瑣的 JavaScript 代碼的過(guò)程。
看看?loading=lazy?到今天(2023-02-26)的兼容性,還是非常不錯(cuò)的:

使用?decoding=async?實(shí)現(xiàn)圖片的異步解碼
除了?loading=lazy,HTML5 還新增了一個(gè)非常有意思的屬性增強(qiáng)圖片的用戶(hù)體驗(yàn)。那就是?decoding?屬性。
HTMLImageElement[19]?接口的?decoding?屬性用于告訴瀏覽器使用何種方式解析圖像數(shù)據(jù)。
它的可選取值如下:
-
sync: 同步解碼圖像,保證與其他內(nèi)容一起顯示。 -
async: 異步解碼圖像,加快顯示其他內(nèi)容。 -
auto: 默認(rèn)模式,表示不偏好解碼模式。由瀏覽器決定哪種方式更適合用戶(hù)。
上文其實(shí)也提及了,瀏覽器在進(jìn)行圖片渲染展示的過(guò)程中,是需要對(duì)圖片文件進(jìn)行解碼的,這一個(gè)過(guò)程快慢與圖片格式有關(guān)。
而如果我們不希望圖片的渲染解碼影響頁(yè)面的其他內(nèi)容的展示,可以使用?decoding=async?選項(xiàng),像是這樣:
<img?src="xxx.png"?decoding="async">
這樣,瀏覽器便會(huì)異步解碼圖像,加快顯示其他內(nèi)容。這是圖片優(yōu)化方案中可選的一環(huán)。
同樣的,我們來(lái)看看到今天(2023-02-26),decoding="async"?的兼容性,整體還是非常不錯(cuò)的,作為漸進(jìn)增強(qiáng)方案使用,是非常好的選擇。

實(shí)際檢驗(yàn)?loading=lazy?與?decoding=async?效果
OK,下面我們制作一個(gè)簡(jiǎn)單的 DEMO,試一下?loading=lazy?與?decoding=async?的效果。
我們準(zhǔn)備一個(gè)擁有 339 個(gè)圖片的 HTML 頁(yè)面,每個(gè)圖片文件的 src 大小不一。
<div?class="g-container">
????<img?src="image1.jpeg">
????<img?src="image2.jpeg">
????//?...?339?個(gè)
</div>
CSS 的設(shè)置也很重要,由于是純圖片頁(yè)面,如果不給圖片設(shè)置默認(rèn)高寬,最頁(yè)面刷新的一瞬間,<img>?元素的高寬都是 0,會(huì)導(dǎo)致所有?<img>?元素都在可視區(qū)內(nèi),所以,我們需要給?<img>?設(shè)置一個(gè)默認(rèn)的高寬:
img?{
????margin:?8px;
????width:?300px;
????height:?200px;
????object-fit:?cover;
}
這樣,再不添加?loading=lazy?與?decoding=async?的狀態(tài)下,看看?Network?的表現(xiàn):

我這里沒(méi)有模擬弱網(wǎng)環(huán)境,網(wǎng)速非常快,可以看到,發(fā)送了 339 個(gè)圖片資源請(qǐng)求,也就是全部的圖片資源在頁(yè)面加載的過(guò)程中都請(qǐng)求了,頁(yè)面?Load?事件完成的時(shí)間為 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?個(gè)
</div>
看看效果:

可以看到,這一次只發(fā)送了 17 個(gè)圖片資源請(qǐng)求,頁(yè)面?Load?事件完成的時(shí)間為 26ms。
| 優(yōu)化前 | 優(yōu)化后 |
|---|---|
| 1.28s | 26 ms |
1.28s 到 26ms,效果是非常明顯的,如果是弱網(wǎng)環(huán)境,對(duì)首屏加載性能的提升,會(huì)更為明顯!
當(dāng)然,實(shí)際我測(cè)試的過(guò)程也,也單獨(dú)試過(guò)?decoding="async"?的作用,只是由于是純圖片頁(yè)面,效果不那么明顯。感興趣的同學(xué),可以自行嘗試。
本章總結(jié)
在本章節(jié)中,我們介紹了不同的方式實(shí)現(xiàn)圖片的懶加載、延遲渲染、異步解碼,它們分別是:
-
通過(guò) onscroll 事件與?
getBoundingClientRect?API 實(shí)現(xiàn)圖片的懶加載方案 - 通過(guò) Intersection Observer(交叉觀察器)實(shí)現(xiàn)比監(jiān)聽(tīng) onscroll 性能更佳的圖片懶加載方案
-
通過(guò)?
content-visibility: auto?實(shí)現(xiàn)圖片資源的延遲渲染 -
通過(guò)?
loading=lazy?HTML 屬性實(shí)現(xiàn)圖片懶加載 -
通過(guò)?
decoding=async?HTML 屬性實(shí)現(xiàn)圖片的異步解碼
圖片資源的容錯(cuò)及可訪問(wèn)性處理
OK,最后一個(gè)章節(jié),我們簡(jiǎn)單聊一聊圖片資源的容錯(cuò)及可訪問(wèn)性處理。
圖片的可訪問(wèn)性處理
可訪問(wèn)性(A11Y),在我們的網(wǎng)站中,屬于非常重要的一環(huán),但是大部分同學(xué)都容易忽視它。
在一些重交互、重邏輯的網(wǎng)站中,我們需要考慮用戶(hù)的使用習(xí)慣、使用場(chǎng)景,從高可訪問(wèn)性的角度考慮,譬如假設(shè)用戶(hù)沒(méi)有鼠標(biāo),僅僅使用鍵盤(pán),能否順暢的使用我們的網(wǎng)站?
非常重要的一點(diǎn)是, 提高可訪問(wèn)性也能讓普通用戶(hù)更容易理解 Web 內(nèi)容 。
基于?Usability & Web Accessibility - image[20]
對(duì)于圖像信息,我們需要大致遵循如下可訪問(wèn)性原則:
- 所有有意義的 img 元素必須有 alt 屬性
- 提供替代 alt 屬性的其他方式
- 使用輔助技術(shù)隱藏裝飾圖像
第一點(diǎn)非常好理解,所有的有意義的圖片元素都必須要提供?alt?屬性。
第二點(diǎn)比較有意思,在 A11Y 中,其實(shí)有一套?WAI-ARIA 標(biāo)準(zhǔn)[21]。WAI-ARIA 是一個(gè)為殘疾人士等提供無(wú)障礙訪問(wèn)動(dòng)態(tài)、可交互Web內(nèi)容的技術(shù)規(guī)范。
簡(jiǎn)單來(lái)說(shuō),它提供了一些屬性,增強(qiáng)標(biāo)簽的語(yǔ)義及行為:
- 可以使用 tabindex 屬性控制元素是否可以聚焦,以及它是否/在何處參與順序鍵盤(pán)導(dǎo)航
-
可以使用 role 屬性,來(lái)標(biāo)識(shí)元素的語(yǔ)義及作用,譬如使用?
<div id="saveChanges" tabindex="0" role="button">Save</div>來(lái)模擬一個(gè)按鈕 -
還有大量的?
aria-*?屬性,表示元素的屬性或狀態(tài),幫助我們進(jìn)一步地識(shí)別以及實(shí)現(xiàn)元素的語(yǔ)義化,優(yōu)化無(wú)障礙體驗(yàn)
上述第二點(diǎn),提供替代 alt 屬性的其他方式?的含義就是使用 WAR-ARIA 規(guī)范提供的諸如?aria-label?和?aria-labelledby?屬性為圖像提供可訪問(wèn)的名稱(chēng)。
當(dāng)存在這些屬性時(shí),輔助技術(shù)(屏幕閱讀器)將忽略圖像的?alt?屬性并讀取 ARIA 標(biāo)簽。
而第三點(diǎn),使用輔助技術(shù)隱藏裝飾圖像,又是什么意思呢?
上面第一點(diǎn)?所有有意義的 img 元素必須有 alt 屬性,反過(guò)來(lái)說(shuō),頁(yè)面上也會(huì)存在無(wú)意義的裝飾性的圖片,這些圖片內(nèi)容對(duì)輔助技術(shù)(屏幕閱讀器)而言,其實(shí)是可以忽略的。
對(duì)于沒(méi)有任何功能或信息內(nèi)容的裝飾圖像,可以通過(guò)多種方式對(duì)屏幕閱讀器隱藏:
-
使用空的?
alt?屬性 -
使用 ARIA 屬性?
role="presentation"?標(biāo)明圖片元素是裝飾可忽略圖片 - 使用 CSS background 的方式呈現(xiàn)這些圖片
alt 不要與 title 混淆
OK,下面來(lái)講一些有意思的細(xì)節(jié)內(nèi)容。
有一個(gè)非常基礎(chǔ)的知識(shí),簡(jiǎn)單過(guò)一下,也就是圖片元素中,alt?與?title?的差異:
-
圖片中的?
alt?屬性是在圖片不能正常顯示時(shí)出現(xiàn)的文本提示。 -
圖片中的?
title?屬性是在鼠標(biāo)在移動(dòng)到元素上的文本提示。
正確使用 alt 屬性
對(duì)于使用屏幕閱讀器的用戶(hù)而言,圖片是無(wú)法正常展示或者被的瀏覽的,基于此,我們需要利用好?alt?屬性,或者是上述的aria-label?和?aria-labelledby?屬性。
那么,這些屬性?xún)?nèi)的內(nèi)容應(yīng)該填充什么呢?我們需要基于圖片的功能加以區(qū)分:
-
信息性圖像[22]:以圖形方式表示概念和信息的圖像,通常是圖片、照片和插圖。
alt?替代文本應(yīng)該至少是一個(gè)簡(jiǎn)短的描述,傳達(dá)圖像所呈現(xiàn)的基本信息。 -
裝飾性圖像[23]:當(dāng)圖像的唯一目的是為頁(yè)面添加視覺(jué)裝飾,而不是傳達(dá)對(duì)理解頁(yè)面很重要的信息時(shí),如上述所言,使用空的 alt,譬如?
alt="" -
功能圖像[24]:用作鏈接或按鈕的圖像的替代文本應(yīng)該描述鏈接或按鈕的功能,而不是視覺(jué)圖像。此類(lèi)圖像的示例是表示打印功能的打印機(jī)圖標(biāo)或提交表單的按鈕。
-
文本圖像[25]:可讀文本有時(shí)會(huì)出現(xiàn)在圖像中。如果圖片不是徽標(biāo),請(qǐng)避免圖片中出現(xiàn)文字。但是,如果使用文本圖像,替代文本應(yīng)包含與圖像中相同的詞。
-
圖形和圖表等復(fù)雜圖像:為了傳達(dá)數(shù)據(jù)或詳細(xì)信息,提供與圖像中提供的數(shù)據(jù)或信息等效的完整文本作為替代文本。[26]
-
圖像組[27]:如果多張圖像傳達(dá)一條信息,則一張圖像的替代文本應(yīng)傳達(dá)整組信息。
-
圖像映射[28]:包含多個(gè)可點(diǎn)擊區(qū)域的圖像的替代文本應(yīng)該為鏈接集提供整體上下文。此外,每個(gè)可單獨(dú)點(diǎn)擊的區(qū)域都應(yīng)該有替代文本來(lái)描述鏈接的目的或目的地。
其實(shí)?alt?的學(xué)問(wèn)是非常之多的,如果我們的頁(yè)面能做到這一點(diǎn),那真的算是從根上開(kāi)始思考,開(kāi)始優(yōu)化用戶(hù)體驗(yàn)。
img 元素與 background 元素的取舍
OK,那么,講到這里,還有一個(gè)有意思的點(diǎn)就很自然的應(yīng)該被提及。
那就是我們應(yīng)該什么時(shí)候使用?<img>?元素,什么時(shí)候使用?background?內(nèi)嵌圖片?
我們可以從性能及功能兩個(gè)方面進(jìn)行考慮:
| 類(lèi)型 | img | backgroud-image |
|---|---|---|
| 圖層位置 | 前景 | 背景 |
| 默認(rèn)初始尺寸 | 不定 | 固定 |
| 是否會(huì)產(chǎn)生回流重繪 | 會(huì) | 不會(huì) |
| 圖片加載失敗 | 可以觸發(fā)元素的 onerror 事件,展示 alt 屬性 | 無(wú)法有效設(shè)置異常處理場(chǎng)景 |
| 使用場(chǎng)景 | Logo、產(chǎn)品圖片、廣告圖片 | 裝飾性無(wú)語(yǔ)義內(nèi)容等 |
其實(shí)性能上并不是核心考慮的點(diǎn),因?yàn)樯衔奈覀円仓v到了在今天可以大規(guī)模使用是?loading="lazy"?屬性,圖片可以進(jìn)行原生支持的懶加載。
我們?cè)诳紤]選取?<img>?還是?backgroud-image?的時(shí)候,更多的還是從圖片功能上進(jìn)行考慮。一般來(lái)說(shuō),作為修飾的且無(wú)語(yǔ)義的裝飾性圖片選擇使用?background-image,而比較重要的與網(wǎng)頁(yè)內(nèi)容相關(guān)的就使用?<img>?標(biāo)簽。
由于有語(yǔ)義的圖片使用?<img>?展示,它的一個(gè)好處在于,當(dāng)圖片加載失敗的時(shí)候,可以觸發(fā)元素的 onerror 事件,我們可以有效的利用這一點(diǎn),對(duì)圖片進(jìn)行異常處理。
圖片的異常處理
當(dāng)圖片鏈接掛了,加載失敗了,我們比較好的處理方式應(yīng)該是怎么樣呢?
處理的方式有很多種。在張?chǎng)涡窭蠋煹倪@篇文章中 --?圖片加載失敗后CSS樣式處理最佳實(shí)踐[29]?有一個(gè)不錯(cuò)的實(shí)踐。
核心思路為:
-
利用圖片加載失敗,觸發(fā)?
<img>?元素的?onerror?事件,給加載失敗的?<img>?元素新增一個(gè)樣式類(lèi) -
利用新增的樣式類(lèi),配合?
<img>?元素的偽元素,在展示默認(rèn)兜底圖的同時(shí),還能一起展示?<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?,加載默認(rèn)錯(cuò)誤兜底圖,利用偽元素?after,展示圖片的?alt?信息:

OK,到此,完整的對(duì)圖片的處理就算完成了,這也比較好的闡述了為什么,對(duì)有語(yǔ)義,有?alt?信息的圖片,我們應(yīng)該使用?<img>?元素來(lái)實(shí)現(xiàn)。這是因?yàn)椋覀兛梢栽阱e(cuò)誤發(fā)生的時(shí)候,比較好的對(duì)圖片進(jìn)行兜底展示,讓用戶(hù)直觀的能夠看到 alt 內(nèi)容。
完整的 Demo 你可以戳這里看看:
CodePen Demo -- 圖片處理 [30]
當(dāng)然,上述方案存在兩個(gè)小問(wèn)題:
-
對(duì)于每一個(gè)?
<img>?元素,我們都需要寫(xiě)一段?onerror="this.classList.add('error');"?代碼,有點(diǎn)重復(fù)。因此,這個(gè)工作也可以交給 JavaScript 全局性的完成,并且,我們可能需要判斷?alt?的值是否為空,在為空時(shí),使用默認(rèn)圖片 alt 兜底文案。 -
早年間,
<img>?等替換元素是沒(méi)有偽元素的,后面 Chrome/Firefox 瀏覽器逐漸支持了當(dāng),<img>?的 src 拉取失敗時(shí),支持?<img>?元素的偽元素展示,這才有了上述的方案,但是,目前 Safari 仍不支持這個(gè)特性,所以,在 Safari 下,我們可能得到如下的結(jié)果:

效果仍然還是 OK 的,只是沒(méi)有了兜底圖的展示,在實(shí)際使用過(guò)程中,需要知道這一點(diǎn)。
最后總結(jié)
本章節(jié),對(duì)圖片資源的容錯(cuò)及可訪問(wèn)性處理進(jìn)行了闡述。核心內(nèi)容在于:
- 對(duì)于圖像信息,我們需要大致遵循如下可訪問(wèn)性原則:
- 所有有意義的 img 元素必須有 alt 屬性
- 提供替代 alt 屬性的其他方式
- 使用輔助技術(shù)隱藏裝飾圖像
至此,整個(gè)現(xiàn)代圖片性能優(yōu)化及體驗(yàn)優(yōu)化指南到此就圓滿(mǎn)結(jié)束,整個(gè)系列的文章囊括了非常多的新的規(guī)范及特性,需要大家在實(shí)踐中根據(jù)實(shí)際情況靈活選取使用。
同時(shí),我們也應(yīng)該能看到,前端技術(shù)僅僅在這一小個(gè)領(lǐng)域,都在不斷的迭代創(chuàng)新。雖然很難,還是需要不斷充實(shí)自己跟上新的潮流。共勉。
最后
OK,本文到此結(jié)束,希望本文對(duì)你有所幫助 :)
想 Get 到最有意思的 CSS 資訊,千萬(wàn)不要錯(cuò)過(guò)我的公眾號(hào) --?iCSS前端趣聞???
更多精彩 CSS 技術(shù)文章匯總在我的?Github -- iCSS[31]?,持續(xù)更新,歡迎點(diǎn)個(gè) star 訂閱收藏。
如果還有什么疑問(wèn)或者建議,可以多多交流,原創(chuàng)文章,文筆有限,才疏學(xué)淺,文中若有不正之處,萬(wàn)望告知。
