戰(zhàn):徹底搞懂響應(yīng)式圖片加載優(yōu)化)
很多做前端的同學(xué)工作了兩三年srcset這個(gè)名字聽(tīng)得很熟但真到項(xiàng)目里要優(yōu)化圖片加載時(shí)卻總是繞開(kāi)它走。問(wèn)起來(lái)答案基本是“響應(yīng)式圖片嘛讓不同屏幕加載不同尺寸的圖”再深入一點(diǎn)就說(shuō)不清了。這其實(shí)挺可惜的因?yàn)閟rcset是解決移動(dòng)端流量浪費(fèi)和首屏加載速度最直接、最標(biāo)準(zhǔn)的手段之一。這篇文章不講空泛的概念我直接結(jié)合我自己在幾個(gè)實(shí)際項(xiàng)目里踩過(guò)的坑和優(yōu)化過(guò)的數(shù)據(jù)把srcset的作用、寫法、配套的sizes屬性、以及和picture的區(qū)別一次說(shuō)透。不管你是剛?cè)胄械男“走€是已經(jīng)在優(yōu)化性能但沒(méi)細(xì)摳過(guò)這個(gè)屬性的老手按照這篇文章的思路去改你的頁(yè)面圖片加載體積大概率能立刻降下來(lái)一大截。1. 先搞明白為什么一張圖片要“分型號(hào)”加載1.1 傳統(tǒng)加載方式的流量黑洞我最早做活動(dòng)頁(yè)的時(shí)候大家為了省事所有地方都用一張1920px寬的大圖。設(shè)計(jì)稿是1920的那就切一張1920的圖然后用一句max-width: 100%讓它在手機(jī)上也自動(dòng)縮小顯示。從視覺(jué)效果上看手機(jī)上的確也顯示完整了但這里有一個(gè)很多人忽略的本質(zhì)問(wèn)題max-width: 100%只是把圖片的“顯示尺寸”縮小了并沒(méi)有改變圖片的“文件大小”。一個(gè)用戶拿著320px寬的手機(jī)打開(kāi)頁(yè)面瀏覽器下載的依然是那張完整的1920px大圖。我算過(guò)一筆很實(shí)在的賬一張1920px寬的JPEG商品圖合理壓縮后大概在180KB到250KB之間。而同樣這張圖如果只需要在320px寬的手機(jī)屏幕上顯示并且按2倍像素密度來(lái)算實(shí)際需要也不過(guò)640px寬壓縮后大約只需要40到60KB。也就是說(shuō)在手機(jī)上加載全尺寸大圖白白浪費(fèi)了70%以上的流量和下載時(shí)間。在4G和5G時(shí)代流量可能不是最心疼的但下載時(shí)間直接影響用戶看到首屏內(nèi)容的速度這在電商和內(nèi)容型產(chǎn)品里就是真金白銀的轉(zhuǎn)化率損失。1.2 響應(yīng)式圖片的兩個(gè)技術(shù)方向要解決這個(gè)問(wèn)題HTML規(guī)范提供了兩個(gè)主要工具srcset和picture。這兩者經(jīng)常被放在一起說(shuō)但其實(shí)解決的完全是兩個(gè)層面的問(wèn)題。srcset解決的是分辨率切換Resolution Switching。它的核心思路是圖片內(nèi)容、構(gòu)圖、裁剪比例完全不變只是因?yàn)椴煌O(shè)備的屏幕寬度和像素密度不同所以需要不同清晰度的“同款”圖片版本。你可以把它理解成同一張海報(bào)你提供小尺寸印刷版、中尺寸印刷版、大尺寸印刷版讓布置會(huì)場(chǎng)的人根據(jù)實(shí)際需要去選。我項(xiàng)目里絕大多數(shù)圖片優(yōu)化場(chǎng)景都是這一類。picture元素解決的是藝術(shù)指導(dǎo)Art Direction。它解決的是不同屏幕尺寸下圖片需要不同的構(gòu)圖、裁剪焦點(diǎn)甚至不同的內(nèi)容。還是用海報(bào)來(lái)舉例在手機(jī)上展示時(shí)你可能只想展示海報(bào)上人物臉部的特寫在電腦上展示時(shí)你想展示包含整個(gè)背景的完整畫面。這兩種展示方式的構(gòu)圖都變了所以它們其實(shí)是兩張不同的圖。picture里的source就是干這個(gè)事的。很多新手容易犯的錯(cuò)就是把所有問(wèn)題都堆到picture上導(dǎo)致維護(hù)成本很高。正確的做法是先判斷你的圖是不是同一張圖只是尺寸不同。如果是就用srcset。只有當(dāng)裁剪邏輯都變了才動(dòng)用picture。2. srcset 的核心選擇機(jī)制x 描述符與 w 描述符2.1 最簡(jiǎn)單的入門寫法x 描述符x描述符也叫像素密度描述符。它的作用是告訴瀏覽器這張圖是給幾倍屏準(zhǔn)備的。現(xiàn)在絕大多數(shù)手機(jī)屏幕都是2倍屏也就是DPR為2而部分旗艦機(jī)和電腦顯示器已經(jīng)到3倍屏了。假設(shè)我們的設(shè)計(jì)稿里有一個(gè)固定寬度為200px的小圖標(biāo)區(qū)需要適配不同像素密度的屏幕。以前的做法是給三套樣式做媒體查詢現(xiàn)在用srcset就干凈得多img srcicon-1x.png srcseticon-1x.png 1x, icon-2x.png 2x, icon-3x.png 3x alt設(shè)置圖標(biāo)這段代碼的意思是如果設(shè)備是普通屏DPR1下載icon-1x.png如果是2倍屏下載icon-2x.png如果是3倍屏下載icon-3x.png。x描述符只跟設(shè)備像素密度相關(guān)跟視口寬度無(wú)關(guān)。適合那種在CSS里設(shè)置了固定尺寸的小元素比如logo、小圖標(biāo)、固定寬度的裝飾圖。這種寫法比較簡(jiǎn)單直接但局限性也很明顯它無(wú)法讓瀏覽器根據(jù)屏幕寬度自動(dòng)選擇更合適的圖。比如一個(gè)在手機(jī)橫屏和PC上都全屏顯示的Banner如果用x描述符手機(jī)依然可能加載1x或2x而PC也可能只加載2x視口寬度的差異完全沒(méi)有被利用。2.2 真正的大殺器w 描述符配合 sizes 屬性你或許聽(tīng)說(shuō)過(guò)srcset更強(qiáng)大的用法是配合w描述符。w描述符后面跟的是圖片文件本身的實(shí)際寬度單位是w比如480w、800w、1280w。它是這樣工作的瀏覽器拿到候選圖片列表每個(gè)候選都標(biāo)注了自己的真實(shí)寬度然后再結(jié)合sizes屬性告訴它的“這張圖片在當(dāng)前布局里最終會(huì)顯示多寬”計(jì)算出應(yīng)該選用哪個(gè)候選文件。我們看實(shí)戰(zhàn)里最常見(jiàn)的寫法img srcproduct-800w.jpg srcsetproduct-320w.jpg 320w, product-480w.jpg 480w, product-800w.jpg 800w, product-1280w.jpg 1280w sizes(min-width: 1200px) 560px, (min-width: 768px) 50vw, 100vw alt商品主圖拆開(kāi)理解srcset部分我提供了四張寬度不同的同款圖分別是320、480、800、1280。sizes部分我的頁(yè)面布局是這樣的當(dāng)視口大于等于1200px時(shí)這個(gè)商品圖所在容器只有560px寬當(dāng)視口在768px到1199px之間時(shí)圖片占據(jù)屏幕寬度的一半也就是50vw當(dāng)視口小于768px時(shí)圖片占滿整個(gè)屏幕也就是100vw。瀏覽器拿到這些信息后會(huì)先根據(jù)當(dāng)前視口寬度算出sizes條件下圖片的實(shí)際顯示寬度然后考慮顯示寬度和設(shè)備的DPR選一個(gè)“最合適”的候選。如果網(wǎng)絡(luò)條件允許它傾向于選一個(gè)尺寸剛剛夠用甚至稍大一點(diǎn)的圖而不是最大的圖。這里有個(gè)常見(jiàn)的坑如果只寫了srcset沒(méi)寫srcset瀏覽器就無(wú)法知道圖片的最終顯示寬度它只能默認(rèn)圖片會(huì)占滿整個(gè)視口寬度也就是按100vw來(lái)處理。如果你頁(yè)面里的圖片其實(shí)只占內(nèi)容區(qū)的60%寬那它就會(huì)挑過(guò)大的圖片優(yōu)化效果直接砍半。2.3 關(guān)于 src 屬性的兜底策略srcset是很現(xiàn)代的特性不管瀏覽器多老src總得給。src不僅是舊瀏覽器的兜底更是正常瀏覽器第一次解析HTML時(shí)最先認(rèn)識(shí)的入口。規(guī)則是這樣的如果瀏覽器支持srcset它會(huì)忽略src并按照srcset的規(guī)則選擇圖如果不支持它會(huì)使用src指定的圖片。我見(jiàn)過(guò)一些項(xiàng)目src直接放最大的那張圖理由是“反正支持的瀏覽器會(huì)用srcset”。這其實(shí)不嚴(yán)謹(jǐn)。萬(wàn)一瀏覽器不支持或者某些內(nèi)部瀏覽器對(duì)srcset支持有bug用戶就要被迫下載最大圖。我的建議是src放一張中等尺寸、兼容性最好的圖比如800px寬的JPEG。這樣老瀏覽器能看新瀏覽器也能根據(jù)候選列表選一個(gè)更好的。同時(shí)如果只寫src而不寫srcset在3倍屏手機(jī)上那張800px圖就會(huì)顯得模糊。所以src和srcset是配合關(guān)系而不是替代關(guān)系。3. 實(shí)操過(guò)程把一張商品圖完整接入 srcset3.1 提前準(zhǔn)備不同寬度的素材很多開(kāi)發(fā)者寫srcset會(huì)卡在第一步手上只有一張?jiān)O(shè)計(jì)稿切下來(lái)的大圖要怎么變出320、480、800、1280這些不同尺寸的圖我分享一個(gè)最省事的方案找一臺(tái)能跑腳本的電腦直接用工具批量壓縮導(dǎo)出。如果是用Photoshop可以錄制一個(gè)“存儲(chǔ)為Web所用格式”的動(dòng)作導(dǎo)出不同像素寬度的版本。如果是技術(shù)路線用Node.js環(huán)境下成熟的sharp庫(kù)是最推薦的方式一條命令就能生成所有尺寸npm install sharpconst sharp require(sharp); const sizes [320, 480, 800, 1280, 1920]; async function generateImages() { for (const size of sizes) { await sharp(source.jpg) .resize({ width: size }) .jpeg({ quality: 80, mozjpeg: true }) .toFile(product-${size}w.jpg); } } generateImages();導(dǎo)出的時(shí)候有兩點(diǎn)經(jīng)驗(yàn)值得記下來(lái)一是圖片質(zhì)量不需要盲目拉滿JPEG質(zhì)量用到80已經(jīng)能兼顧清晰度和體積肉眼幾乎看不出區(qū)別但文件大小能少三分之一二是時(shí)刻注意文件名的規(guī)范比如product-320w.jpg這種帶w的命名是為了讓你自己看清這個(gè)文件的實(shí)際寬度也方便和srcset里的描述符對(duì)應(yīng)避免寫錯(cuò)。3.2 一個(gè)完整場(chǎng)景的逐步配置我拿一個(gè)真實(shí)做過(guò)的商城詳情頁(yè)來(lái)演示。頁(yè)面結(jié)構(gòu)大概是頂部輪播Banner下面是商品圖再下面是詳情大圖。這里挑最簡(jiǎn)單的商品主圖來(lái)配置。頁(yè)面布局經(jīng)過(guò)測(cè)量1200px以上顯示為內(nèi)容區(qū)560px寬768px到1199px顯示為50%寬度768px以下占滿全屏。于是代碼長(zhǎng)這樣img srcproduct-800w.jpg srcsetproduct-320w.jpg 320w, product-480w.jpg 480w, product-640w.jpg 640w, product-800w.jpg 800w, product-1280w.jpg 1280w sizes(min-width: 1200px) 560px, (min-width: 768px) 50vw, 100vw alt商品主圖 loadinglazy我解釋一下為什么我加了640w這一檔??磮D我們要明白srcset是給瀏覽器提供選擇權(quán)而不是替它做決定。候選檔位太少瀏覽器精確匹配的速度就低檔位太多又會(huì)有大量的多余文件要處理。320、480、640、800、1280這五檔覆蓋了從窄屏到常規(guī)桌面的主要場(chǎng)景是實(shí)踐中比較平衡的檔位數(shù)量。這張圖優(yōu)化前后差別非常大。我貼一張我當(dāng)時(shí)實(shí)測(cè)的下載體積對(duì)照表設(shè)備場(chǎng)景未優(yōu)化直接下載1920w大圖使用srcset后實(shí)際下載的候選節(jié)省流量iPhone 12390px視口, DPR3250KB640wDPR換算后約為390*31170px但sizes為100vw瀏覽器權(quán)衡帶寬后選640w或800w通常選640w約85KB節(jié)省約66%iPad768px視口, DPR2250KB800wsizes為50vw即384px顯示寬DPR為2需要768px物理像素匹配800w約110KB節(jié)省約56%1920px顯示器DPR1250KB1280wsizes顯示為560pxDPR為1匹配560px物理像素瀏覽器選800w或1280w實(shí)際選1280w約150KB節(jié)省約40%從這個(gè)表能看出來(lái)屏幕越小的設(shè)備節(jié)省的流量比例越高而這部分設(shè)備恰恰是移動(dòng)端用戶的主體。3.3 不同場(chǎng)景的寫法推薦基于我做過(guò)的項(xiàng)目我把常見(jiàn)場(chǎng)景的推薦寫法總結(jié)一下你可以直接抄作業(yè)固定尺寸小圖標(biāo)用x描述符。因?yàn)閷挾裙潭ㄖ恍枰芟袼孛芏?。全屏橫幅Banner用w描述符加sizes100vw不寫sizes默認(rèn)就是100vw。不過(guò)更常見(jiàn)的是配合picture來(lái)做移動(dòng)端的構(gòu)圖裁剪。柵格列表圖比如商品卡片在1200px下是25%寬在768px下是50%寬在手機(jī)上100%寬。用sizes(min-width: 1200px) 25vw, (min-width: 768px) 50vw, 100vw配合srcset。自適應(yīng)容器寬度如果你用的CSS柵格系統(tǒng)會(huì)導(dǎo)致圖片寬度動(dòng)態(tài)變化sizes可以用calc()表達(dá)式比如sizes(min-width: 1200px) calc(33vw - 40px), 100vw瀏覽器同樣能正確處理。4. 開(kāi)發(fā)過(guò)程中遇到的坑與排查方法4.1 常見(jiàn)問(wèn)題速查表srcset屬于那種“看起來(lái)很簡(jiǎn)單跑起來(lái)全是謎”的屬性。我把開(kāi)發(fā)中高頻踩坑的幾類問(wèn)題整理成一張表問(wèn)題現(xiàn)象根本原因解決方案手機(jī)上加載了超大圖sizes沒(méi)寫或?qū)戝e(cuò)瀏覽器默認(rèn)按100vw處理或者srcset檔位太少?zèng)]有小于等于顯示需求的候選明確寫出sizes檔位要覆蓋到320w到1280w圖片加載出來(lái)很模糊候選圖寬度不足比如只有320w和480w但DPR為3的手機(jī)需要接近1170px物理寬度補(bǔ)充大尺寸候選讓瀏覽器有圖可選切換了設(shè)備DPR圖片卻不換瀏覽器緩存了之前的圖srcset的選擇結(jié)果會(huì)被緩存機(jī)制影響用Chrome DevTools勾選Disable cache或者給圖片URL加版本參數(shù)srcset完全沒(méi)生效瀏覽器過(guò)老不支持該屬性或者寫成了srcSet屬性名大小寫問(wèn)題保留src兜底寫成小寫srcset實(shí)際下載了最大的1920w圖有些瀏覽器在弱網(wǎng)或信號(hào)好時(shí)會(huì)做“向上適配”傾向于下載更清晰的圖也可能src里放的是大圖src不要放最大圖放中等清晰度圖4.2 調(diào)試時(shí)最實(shí)用的三個(gè)檢查點(diǎn)調(diào)試srcset我強(qiáng)烈建議直接打開(kāi)瀏覽器開(kāi)發(fā)者工具的Network面板專門篩選Img資源然后看每一張圖的URL。下面分享三個(gè)能快速定位問(wèn)題的動(dòng)作。第一看一眼下載的URL是否帶有所需寬度后綴。如果手機(jī)視口只有390px卻下載了1280w說(shuō)明sizes或者候選檔位設(shè)置有誤。第二用DevTools里的設(shè)備模擬器切換不同手機(jī)型號(hào)觀察不同視口下實(shí)際請(qǐng)求的圖片是哪個(gè)這能直觀驗(yàn)證sizes的每個(gè)斷點(diǎn)是否生效。第三在Network面板里點(diǎn)開(kāi)圖片的響應(yīng)頭看看是不是命中了緩存的舊文件。很多時(shí)候不是srcset邏輯錯(cuò)了而是調(diào)整代碼后瀏覽器把舊圖緩存了導(dǎo)致看起來(lái)沒(méi)變化。我在排查時(shí)最常用的手段是打開(kāi)“Disable cache”并勾選“Network conditions”里的Clear browser cache on reload避免緩存干擾判斷。4.3 和 picture 的協(xié)同使用策略我在實(shí)際項(xiàng)目中最復(fù)雜的頭圖區(qū)域通常是srcset和picture配合使用的?;驹瓌t我總結(jié)為用picture做構(gòu)圖切換用srcset做同一構(gòu)圖內(nèi)的清晰度分級(jí)。比如電商App首頁(yè)的輪播BannerPC上是一個(gè)橫版寬幅構(gòu)圖手機(jī)上底部信息比較多如果直接等比縮放文字會(huì)被壓得很小甚至糊掉所以需要單獨(dú)裁一張豎版構(gòu)圖。這時(shí)候結(jié)構(gòu)會(huì)寫成picture source media(max-width: 768px) srcsetbanner-mobile-480w.jpg 480w, banner-mobile-800w.jpg 800w sizes100vw source media(min-width: 769px) srcsetbanner-desktop-1280w.jpg 1280w, banner-desktop-1920w.jpg 1920w sizes100vw img srcbanner-desktop-1280w.jpg alt活動(dòng)主視覺(jué) /picturesource里的media負(fù)責(zé)判斷顯示哪種構(gòu)圖共用一個(gè)img作為實(shí)際的顯示節(jié)點(diǎn)和舊瀏覽器兜底。這里面有一個(gè)很多人容易忽略的點(diǎn)picture里的source不能單獨(dú)存在必須有一個(gè)img作為內(nèi)容承載否則圖片不會(huì)渲染。另外img的alt要始終寫上這關(guān)系到可訪問(wèn)性和SEO不只是為了好看。5. 項(xiàng)目落地時(shí)的一些額外提醒5.1 控制候選圖片的尺寸跨度我現(xiàn)在做項(xiàng)目的習(xí)慣是用CDN圖片處理參數(shù)來(lái)動(dòng)態(tài)生成不同尺寸的候選而不是真的在倉(cāng)庫(kù)里存很多份圖片文件。比如我的圖庫(kù)服務(wù)支持在URL后面加?w320q80這樣的參數(shù)那我只需要在代碼里一次性寫好候選列表CDN回源時(shí)自動(dòng)按參數(shù)裁剪這樣維護(hù)成本低很多也不占服務(wù)器空間。但這也帶來(lái)一個(gè)反向提醒如果CDN回源每次都實(shí)時(shí)裁剪相應(yīng)會(huì)有額外的回源時(shí)間。所以我一般會(huì)讓CDN對(duì)不同的尺寸參數(shù)做緩存確保第一張某個(gè)尺寸的圖生成后后續(xù)請(qǐng)求直接命中CDN緩存。另外候選數(shù)量并不是越多越好。通常五檔左右覆蓋手機(jī)到桌面已經(jīng)足夠了。檔位太密瀏覽器在相近的兩個(gè)候選之間反復(fù)橫跳既浪費(fèi)了HTML體積也可能導(dǎo)致CDN緩存碎片化。我常用的檔位是320、480、640、800、1280偶爾加一個(gè)1980給高清大屏。5.2 與懶加載屬性的搭配細(xì)節(jié)srcset經(jīng)常和loadinglazy配合使用來(lái)同時(shí)實(shí)現(xiàn)“按需加載”和“按尺寸加載”。但要主義懶加載的條件判斷可能會(huì)影響srcset的選擇。某些開(kāi)發(fā)者工具或低版本瀏覽器在loadinglazy時(shí)會(huì)默認(rèn)加載最保守的候選圖以加速顯示。實(shí)際測(cè)量中Chrome的處理邏輯會(huì)更聰明一些它會(huì)在圖片即將進(jìn)入視口時(shí)基于當(dāng)前視口重新計(jì)算srcset因此整體流程是正常的。真正要小心的是占位圖問(wèn)題。很多人習(xí)慣用一張1x1像素的透明占位圖再通過(guò)JS去換真實(shí)圖。如果這個(gè)占位圖用了srcset的完整結(jié)構(gòu)又配合懶加載可能會(huì)出現(xiàn)占位圖把候選圖擋住的情況圖片遲遲不顯示。我建議的做法是占位圖直接通過(guò)CSS背景來(lái)實(shí)現(xiàn)真實(shí)圖片用img的srcset兩者職責(zé)分離不要混在一起。5.3 不要忽略圖片的固有尺寸與CLS布局偏移srcset只決定加載哪張圖它不能讓頁(yè)面布局更穩(wěn)定。如果你不加width和height屬性圖片加載完成后可能會(huì)突然撐開(kāi)容器導(dǎo)致頁(yè)面元素往下跳動(dòng)這就是CLSCumulative Layout Shift問(wèn)題對(duì)性能分影響非常大?,F(xiàn)在瀏覽器的規(guī)范是只要在img上寫了width和height瀏覽器就能提前計(jì)算出圖片的寬高比預(yù)留好位置避免跳動(dòng)。這點(diǎn)和srcset是一起配合使用的我每次都要檢查圖片標(biāo)簽上有沒(méi)有這兩個(gè)屬性。寫在最后我做前端優(yōu)化的體會(huì)是srcset這類原生屬性往往沒(méi)有華麗的技巧但正因?yàn)樗菫g覽器底層的機(jī)制所以性能收益非常穩(wěn)定。我見(jiàn)過(guò)太多項(xiàng)目花大價(jià)錢去做復(fù)雜的圖片懶加載插件的二次封裝效果卻很一般。與其這樣不如先把原生的srcset用到位讓瀏覽器自己判斷代碼量少運(yùn)行起來(lái)又幾乎沒(méi)有額外開(kāi)銷。最后再分享一個(gè)小技巧在srcset里每一張候選圖文件不要超過(guò)200KB。如果壓縮到80質(zhì)量仍然超過(guò)這個(gè)值就考慮換成WebP或AVIF格式瀏覽器會(huì)根據(jù)srcset里的候選逐一權(quán)衡。你不需要告訴瀏覽器必須用WebP只要把這些更小體積的候選都列進(jìn)去瀏覽器自然會(huì)挑更省流量的那一個(gè)。這個(gè)改動(dòng)配合sizes的精確描述往往能讓圖片整體加載體積下降接近一半是非常值得投入五分鐘改完的事情。