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