如何利用Webp和http快取節省30%的網路流量?

志如發表於2019-03-01

  Webp推出那年,我剛剛考上高中。轉眼間,大學畢業將近一年,我依舊是那個青蔥少年!就像Webp一樣,還是那麼年輕,時至今日尚未嶄露頭角,原因是各大瀏覽器對它的相容依舊不是那麼的友好。IE爸爸甚至至今都沒有要支援它的跡象。

webp

維基百科:WebP最初在2010年釋出,目標是減少檔案大小,但達到和JPEG格式相同的圖片品質,希望能夠減少圖片檔在網路上的傳送時間。 2011年11月8日,Google開始讓WebP支援無失真壓縮和透明色(alpha通道)的功能,而在2012年8月16日的引用實做libwebp 0.2.0中正式支援。根據Google較早的測試,WebP的無失真壓縮比網路上找到的PNG檔少了45%的檔案大小,即使這些PNG檔在使用pngcrush和PNGOUT處理過,WebP還是可以減少28%的檔案大小。

  簡單來說,Webp格式是一種圖片格式,它有更優秀的圖片壓縮演算法,並且能實現肉眼難以辨識的質量差異,同時它還支援有損無損兩種壓縮模式。

  由於是谷歌的親兒子,所以安卓原生瀏覽器對Webp的支援還是比較樂觀的,Chrome桌面版和安卓版的支援也都比較好。國內的瀏覽器也不同程度的對Webp做了很多支援。我去Can I Use上截了張圖過來:

如何利用Webp和http快取節省30%的網路流量?

  由此可見,市面上佔有率比較大的瀏覽器對Webp的支援還是很不錯的,所以有必要使用起來。其實是之前無意間發現某寶和某東在使用,所以也想在這塊做一些優化。

  有圖有真相,先看看優化後的效果吧。

  使用前:

如何利用Webp和http快取節省30%的網路流量?
  使用後:
如何利用Webp和http快取節省30%的網路流量?
  可以很明顯的看到,僅僅這八張並不是特別大的圖片,便節省了59.3K的流量。下載時間也有明顯的縮短。如果你的web專案是類似於某寶某東那樣有著大量圖片,那麼這塊節省的流量可想而知!

  市面上有一些圖片格式轉換工具,我這裡就不一一列舉了。這裡要講的可能是比較簡單的一種使用方式,因為我們的圖片等資原始檔託管在阿里雲oss上,它只需要你在請求url裡面帶個引數,就會自動返回你想要的圖片格式。各位看官,如果你們的情況和我不一樣,可能需要自己對圖片做一部分處理或者別的雲端儲存也有類似的解決方案。

  好的,言歸正傳,接下來說說我的解決方案。我們的Web是利用Vue實現的前後端同構的,所以存在服務端渲染和客戶端渲染兩種情況,這就要求我們要分別在服務端和客戶端對瀏覽器是否支援Webp作出判斷,如果瀏覽器支援,就去oss取webp格式的圖片,否則繼續使用原本圖片格式。

  我所採取的方法是在封裝網路請求的時候,做了一步判斷,然後把是否支援Webp的變數放到了環境變數中。由於網路請求需要同時支援客戶端和服務端,所以我採用的是axios並自己做了一層封裝。

//  封裝axios
createRequest = (req) => {
    // 如果在客戶端建立
    if (process.client) {
        process.env.supportWebp = document.createElement('canvas').toDataURL('image/webp').indexOf('data:image/webp') === 0
    } else {
     //  從服務端檢測客戶端是否支援webp
        if (req && req.headers) {
            process.env.supportWebp = req.headers.accept.indexOf('image/webp') > -1
        }else{
            process.env.supportWebp = false
        }
    }
}

複製程式碼

  其實無論是客戶端還是服務端,都可以採用判斷accept裡面是否帶有'image/webp'的方式,但是有些童鞋說判斷accept方式有些瀏覽器不準確,所以我們在客戶端採用較為穩妥的方式去判斷。

  每個人的框架或者環境可能不同,所以程式碼不一定能照搬,只需理解這部分的思想:根據不同的環境判斷瀏覽器是否支援Webp。

  在使用的時候,對於頁面中的img,我寫了一個過濾器:

<img :src="item.image_url | checkWebp">
複製程式碼
export function judgeWebp (src) {
  if(process.env.supportWebp + '' === 'true'){
    return src + '?x-oss-process=image/format,webp'
  }
  return  src
}
const filters = {
  //......,
  judgeWebp
}
Object.keys(filters).forEach(key => {
  Vue.filter(key, filters[key])
})

複製程式碼

  對於背景圖片,style動態繫結似乎是不能使用過濾器的,所以採用計算屬性的方式實現。各位看官如果有更好的方法歡迎提出來。

  到此為止,我們可以根據瀏覽器是否支援webp來獲取到不同格式的圖片了。另外,有些社群也有過利用第三方polyfill來實現瀏覽器相容Webp的方案。但是似乎並不是那麼的流行,追求穩妥的情況下,我這裡暫不使用,如果你有過類似的實踐,歡迎與我分享。

http快取

  webp的分享就到這裡,接下來我們簡單聊聊http快取。http快取大致分為兩類,一類是強制快取,另一類叫對比快取。這兩種快取方式是可以同時存在的。強制快取,一聽這名字就威武霸氣,所以它的優先順序也是比較高的,就是說,如果強制快取生效,對比快取就不再執行。另一個區別點就是,強制快取如果生效,就不再和伺服器互動了,對比快取則需要每次都和伺服器互動協商。

強制快取

  先說強制快取。瀏覽器向伺服器請求資料,返回的header頭中會攜帶快取規則。體現在Expires和Cache-Control這兩個屬性當中。

  Expires是HTTP 1.0的東西,可以說是歷史遺留產物了。它的值是到期時間,如果請求時間小於這個到期時間,就會採用快取。我們一眼就能發現這個邏輯其實意義並不大,而且如果服務端和客戶端時間不一致,會有誤差產生。

  Cache-Control似乎是為彌補Expires的天生缺陷而生的。它倆如果同時存在,Expires則不會生效。它的取值可以為:

取值 含義
private 可被快取,但不能在使用者之間共享
public 可被快取,並且在多使用者間共享
no-store 不快取
no-cache 使用對比快取與伺服器互動
max-age=xxx 設定快取有效期(單位秒)

  這裡需要區別no-store和no-cache,謹記no-store是不做快取,而no-cache是使用對比快取。似乎翻譯過來很像,但是實際效果差很多,對於no-store這種不快取,除非特殊情況,我們一般不使用。

對比快取

  我們再聊聊對比快取。對比快取主要分兩大塊,一塊兒是根據修改時間判斷快取是否生效,另一塊是通過Etag(個人理解就是個hash值)來判斷。

Last-Modified && If-Modified-Since

  Last-Modified是存在於返回的header中的,顧名思義,它告訴我們這個資源的最後修改時間。當瀏覽器再次發起請求的時候,會由If-Modified-Since帶著這個值到伺服器去做對比,如果伺服器發現這個值小於目前伺服器上資源的Last-Modified,則會把新的檔案返回,狀態碼200。如果大於等於則只返回攜帶304狀態碼的請求,通知瀏覽器這個值尚未失效。

  它的缺點是這裡的時間值只能精確到秒。

Etag && If-None-Match

  Etag可以理解為伺服器給資源打的hash值,就類似於我們使用構建工具打包資原始檔後面會跟一條常常的字串一樣,它保證資原始檔的唯一性。Etag隨response返回給瀏覽器,同理瀏覽器下一次請求會由If-None-Match攜帶Etag的值去到伺服器作比對。如果發現這個值不存在,說明本地的資原始檔已經失效,伺服器返回新的資原始檔給客戶端,狀態碼200。反之,返回304通知客戶端資原始檔仍然生效。

  相較於對比最後修改時間的策略,它的優點在於可以突破精確到秒的限制,另外如果我們有一些定期更新的檔案,但是資源內容不變,Etag的優勢就更為明顯了。

  這裡要提及的一點是,當Last-Modified和Etag策略同時生效的時候,Etag的優先順序更高。

結語

  本來是想記錄一下webp的,但是既然是優化,就順便寫了寫http快取這塊的內容。除此之外,各位還可以根據實際情況合理配置cdn以及nginx等的快取,以實現更好的使用者體驗。優化路上無止境,但願我們都能往極致的方向去做。今天就聊到這兒,有什麼說錯的地方還請各位看官批評指正,希望大家多多指教!

相關文章