本文首發於 vivo互聯網技術 微信公衆號
連接: https://mp.weixin.qq.com/s/rSpWorfNTajtqq_pd7H-nw
做者:悟空中臺研發團隊
移動端網頁的加載速度對用戶體驗極爲重要,是影響頁面轉化率的關鍵因素,H5 活動頁每每使用大量的圖片素材來豐富活動效果,素材加載的快慢會對用戶感知形成重要的影響。javascript
在《悟空活動中臺 - H5 活動加載優化》一文中咱們提到過圖片壓縮也是提高悟空中臺產出 H5 頁面加載性能的重要手段之一,對本篇將從技術選型、架構設計到方案落地,全方位的呈現悟空活動中臺基於 WebP 的圖片高性能加載方案。前端
包含了大量圖片素材的 H5 頁面,呈現給用戶以前,至少要等待首屏加載完成;要提高加載速度,一方面請求的響應速度要足夠快,另外一方面要儘可能減少傳輸的數據量。java
原始作法是:拿到圖片文件後,使用圖片壓縮工具進行壓縮,頁面再引入壓縮後的小體積文件;node
該方案存在嚴重的問題:效率低下 ——須要開發者或者設計師針對每張素材圖進行手動壓縮、肉眼審覈質量、壓縮獲得的文件手動上傳。linux
咱們從高清晰度、高壓縮比、小體積的訴求出發,最終選擇了使用 WebP 做爲首選圖片文件格式。git
WebP 是 Google 推出的一種同時提供了有損壓縮與無損壓縮(可逆壓縮)的圖片文件格式。派生自影像編碼格式 VP8,被認爲是 WebM 多媒體格式的姊妹項目,是由 Google 在購買 On2 Technologies 後發展出來,以 BSD 受權條款發佈。github
WebP 的優點體如今它具備更優的圖像數據壓縮算法,能帶來更小的圖片體積,並且擁有肉眼識別無差別的圖像質量;同時具有了無損和有損的壓縮模式、Alpha 透明以及動畫的特性,在 JPEG 和 PNG 上的轉化效果都至關優秀、穩定和統一。web
相比於其餘相同大小、不一樣格式的壓縮圖像,WebP 格式的圖片擁有更小的體積以及更高的質量,優點十分明顯。算法
下圖是一些實測案例:數據庫
使用 WebP 對圖片進行有損壓縮,在默認配置 75% 的壓縮比下,能夠將 PNG 圖片大小壓縮至原圖體積的 13% 左右,JPG 圖片甚至能夠壓縮至原圖體積的 10% 左右(可參考官方測試頁面),實際效果顯著。
悟空中臺的素材服務架構以下圖所示,在 node server 節點中,咱們集成了圖片轉 WebP 以及轉碼後文件存儲的服務。
圖片壓縮服務實現了將用戶上傳的圖片數據,進行格式校驗、WebP 格式轉碼、上傳文件服務器以及存儲的過程。
cwebp 是 Google 官方提供的用於將 PNG、JPEG、TIFF 或原始 Y'CbCr 格式的文件壓縮轉換爲 WebP 格式的命令行編碼工具(安裝方法請參考官網安裝說明)。
使用方法以下:
cwebp [options] input_file -o output_file.webp
其中 options 是壓縮參數配置,包含是否啓用無損壓縮 -lossless ,壓縮係數 -q(0~100) 等,如使用 80 的壓縮係數對目標文件進行有損壓縮:
cwebp -q 80 image.png -o image.webp
cwebp-bin 模塊提供了 Node.js 使用 cwebp 能力進行圖片壓縮轉碼的接口,咱們的圖片壓縮服務引入該模塊模塊實現常見格式圖片到WebP的轉碼。
(1)工具安裝
首先須要在服務器執行下述指令以安裝模塊內部集成的 WebP 工具程序(libwebp-x.x.tar.gz):
npm install --global cwebp-bin
(2)網絡優化
在實際使用時,打包上線時會偶發該安裝包資源請求失敗的問題;爲了安裝過程的順利進行,悟空中臺的開發者將該安裝包的url由原github下載地址改成了更加穩定的google官方下載地址:
// node_modules/@vivo/cwebp-bin/lib/install.js - line 14 binBuild.file(path.resolve(__dirname, '../vendor/source/libwebp-1.1.0.tar.gz'), [ `./configure --disable-shared --prefix="${bin.dest()}" --bindir="${bin.dest()}"`, 'make && make install' ]).then(() => { ... }).catch(error => { ... });
改成
// node_modules/@vivo/cwebp-bin/lib/install.js - line 14 var cfg = [ './configure --disable-shared --prefix="' + bin.dest() + '"', '--bindir="' + bin.dest() + '"' ].join(' '); var builder = new BinBuild() .src('http://downloads.webmproject.org/releases/webp/libwebp-0.5.1.tar.gz') .cmd(cfg) .cmd('make && make install'); return builder.run(function (err) { ... });
(3)圖片壓縮
圖片壓縮服務中使用如下代碼調用 cwebp 工具進行原圖到 WebP 的轉碼:
const {execFileSync} = require('child_process'); const cwebp = require('cwebp-bin'); execFileSync(cwebp, ['input.png', '-o', 'output.webp'])
經過上文咱們瞭解到,WebP支持 有損壓縮 和 無損壓縮 兩種形式,下面咱們將針對性的測試兩種壓縮形式的差別並選出適合的方案。
之因此要對比有損與無損的區別,主要是考慮到時間上的效率和空間上的節約。若是在損失 20~30% 的精度後,用戶的肉眼上難以區分,那麼這個精度的損失就是有意義的,由於相對於無損壓縮,有損壓縮帶來的體積的縮小以及壓縮時的效率,都比無損壓縮更適合用於企業的生產模式下。
咱們選取了特色分別爲 色彩單一 、 色彩較爲豐富 和 色彩極爲豐富 的三張圖片進行測試:
下面列出了上述圖片分別使用 WebP 無損和有損壓縮進行測試的樣本數據。
(1)WebP無損壓縮:
execFileSync(cwebp, ['-lossless', filePath, '-o', webpPath]);
結果統計
(2)WebP有損壓縮(90%壓縮率):
execFileSync(cwebp, ['-q', '90', filePath, '-o', webpPath]);
結果統計
根據上面兩份測試數據能夠得出,對於 同一張圖片:
對於 不一樣圖片,色彩 越豐富,壓縮花費的 時間越長 ,壓縮比越小;甚至會出現壓縮的到的圖片體積超過原圖的狀況(具體緣由見下文)。
經過以上測試數據反映的結果來看,有損壓縮 的優點更大。
使用 WebP 有損壓縮來進行圖片的壓縮,就不得不考慮接下來的問題:WebP 壓縮比設置爲多少纔是最佳實踐?
一樣的,咱們對上述圖片進行了如下抽樣數據對比:
(1)Webp 有損壓縮(90%壓縮率):
execFileSync(cwebp, ['-q', '90', filePath, '-o', webpPath]);
結果統計
(2)Webp 有損壓縮(默認值 75%壓縮率):
execFileSync(cwebp, ['-q', '75', filePath, '-o', webpPath]);
結果統計
爲何要拿 75% 壓縮率來作對比?緣由是 cwebp 有損壓縮的默認壓縮率是 75%,這個比例也是一般狀況下官方推薦的。
可是在實際業務場景下,75% 的壓縮比例並不能知足產品需求。好比說一張圖片通過壓縮後同時在移動端和 PC 端使用;或圖片的色彩空間尤爲複雜等等這些狀況,再通過 75% 的有損壓縮,咱們觀察到色彩對比度明顯的圖片局部有模糊的狀況。
通過與設計師同窗一塊兒反覆的測試實驗,咱們使用了 90% 的壓縮率來代替默認的 75% 。此時轉換後的圖片與原圖片結構化差別值 SSIM 不會小於 0.88 ,視覺效果上用戶基本發現不了圖片已經進行了壓縮。
結構類似性指標(英文:structural similarity index,SSIM index)是一種用以衡量兩張數位影像類似程度的指標。當兩張影像其中一張爲無失真影像,另外一張爲失真後的影像,兩者的結構類似性能夠當作是失真影像的影像品質衡量指標。相較於傳統所使用的影像品質衡量指標,像是峯值信噪比(英語:PSNR),結構類似性在影像品質的衡量上更能符合人眼對影像品質的判斷。
關於 WebP 壓縮質量與 SSIM 的比例關係,請參考 Google 官方說明 WebP Compression Study。
咱們能夠經過在 cwebp 的執行命令中加入 -print_ssim 選項,令壓縮結果中呈現 SSIM 信息:
await execFileSync(cwebp, ['-print_ssim', '-q', '90', filePath, '-o', webpPath])
執行輸出信息:
咱們在測試的過程當中還觀察到有一些圖片轉換爲 WebP 格式後獲得文件體積比原圖更大。通過查閱 Google 官方文檔,得出是因爲格式差別以及轉碼算法致使的:
WebP 的壓縮率設置超過 75%時,在遇到在遇到一些特殊編碼的圖片時,會調整壓縮時的算法,如:
面對這個問題,咱們與設計和產品同事共同制定了相應策略:若是壓縮後的文件體積大於原圖,則使用原圖。
在肯定合適的壓縮比例和壓縮方案後,就能夠對圖片壓縮服務進行總體設計,流程以下:
前端頁面策略是當網頁運行在支持 WebP 格式的宿主環境(如 Chrome、Android Webview 等)中時,優先使用 WebP 圖片資源,在不支持的宿主環境中,使用原始圖片資源。
頁面首先須要判斷當前宿主環境是否支持 WebP :
const supportWebP = (function () { var canvas = typeof document === 'object' ? document.createElement('canvas') : {} canvas.width = canvas.height = 1 return canvas.toDataURL ? canvas.toDataURL('image/webp').indexOf('image/webp') === 5 : false })()
前面講解了後臺圖片壓縮和存儲服務的設計,接下來咱們來一塊兒瞭解一下前端邏輯上是如何加載 WebP 圖片的。其流程以下圖所示:
獲取圖片 url 的方式有多種,咱們的需求是在圖片資源加載前獲取真實的圖片 url,並對其進行處理,而 Vue 提供的自定義指令能夠幫助咱們以侵入性極小的形式的拿到目標元素的相關信息。
這裏咱們使用 bind 指令進行一次性的初始化設置,在當指令第一次綁定到元素時調用,經過獲取到元素關聯的素材的 url,以 img 元素爲例:
bind: function (el, binding) { if (el.tagName.toLowerCase() === 'img' && el.src && el.src.indexOf('data:image') === -1 && supportWebP) { // 經過 src 屬性獲取 img 元素關聯的圖片地址 var _src = el.src // ... 對img的後續處理 } }
首先判斷當前 url 中是否有素材上傳時標記的「nwebp」字樣,若是有則說明該圖片轉爲 WebP 格式後體積反而大於原圖,此時無需使用 WebP 素材替換原有素材;不然,則加載體積更小的 WebP 文件代替原素材文件。
而後判斷當前運行環境是否支持 WebP 格式圖片的渲染,若是支持,則加載 WebP 素材資源,不然使用原文件連接。
咱們在 img 標籤上添加上文定義的 v-webp 指令以下:
<img src="https://someurl" v-webp />
在 img 元素的 create 階段, v-webp 指令被 bind 並執行定義好的 hook。
在 hook 中,咱們對於 img 元素咱們能夠根據 el.src 獲取到元素關聯素材的 url,當判斷須要採用 WebP 格式文件時,在原素材 url 後拼接.webp,從而使得對應圖片元素加載的是 WebP 編碼後的素材:
// ... 對img的後續處理 // 帶有 nwebp 標記的圖片不作轉換 if (_src.indexOf('nwebp') > -1) { return } let webpSrc = '' if (_src.indexOf('.webp') > -1) { webpSrc = _src } else { webpSrc = _src + '.webp' } el.src = webpSrc el.onerror = function() { // WebP加載失敗則回退至源文件 el.src = _src }
對於運行環境不支持 WebP 加載的狀況,則無需作任何處理,直接加載原圖便可:
if (!supportWebP) { return }
對於 img 以外的元素,咱們在 v-webp 指令中傳入要做爲 backgroundImage 屬性值的 url:
<div v-webp="https://someurl"></div>
在 hook 中,根據 binding.value 獲取指令的綁定值,即圖片 url,當判斷須要採用 WebP 格式文件時,在原素材 url 後拼接「.webp」 構造頁面用 url,不然直接使用原圖 url,而後爲該 DOM 元素設置內聯的 backgroundImage style 便可:
if (supportWebP) { el.style.backgroundImage = 'url("' + webpSrc + '")' } else { el.style.backgroundImage = 'url("' + binding.value + '")' }
WebP 格式雖然優勢衆多,可是有一個嚴重的問題—— 兼容性 並不理想。下面咱們將從 「擴展WebP兼容範圍」 的訴求出發,探索 前端解碼WebP文件 的可行性。
WebP 格式雖然存在壓縮率高、體積小等優點,可是其自身並非通用瀏覽器圖片格式規範,像 Safari 和 FireFox 等宿主環境均沒有很好的支持該格式(參考自can i use):
爲了保證悟空中臺產出的專題頁在更多的瀏覽器中可以以更快的速度加載、渲染,咱們又向前走了一步,對 WebP 格式的純前端解碼作出了下面的探索。
核心理念是將 WebP 圖片做爲傳輸介質,保證了頁面圖片數據的下載速度;在拿到 WebP 圖片後,對於不支持的宿主環境,將 WebP 圖片進行解碼成通用的 Base64 格式進行渲染。
純前端是否能夠實現 WebP 格式到 Base64 格式的解碼呢?Google 官方團隊提供了 js 解碼 WebP 的庫—— libwebp.js ;可是咱們隨機挑選一些 WebP 圖片實際測試下來發現性能欠佳:
該方案下 WebP 圖片實際的加載時間爲 網絡數據傳輸用時 + 解碼用時,面對性能要求較高的場景,WebP 的加載速度真要受限於 JS 不擅長的編解碼運算能力了麼?當咱們再次研究 libwebp 的資料時,瀏覽到下述說明:
webp_js 還有一個 WebAssembly 版本。
WebAssembly 做爲 Web 標準,在各個瀏覽器均有較好的支持,兼容性遠強於WebP:
WebAssembly 能夠做爲 C/C++/Rust 等語言的編譯目標在瀏覽器環境中以接近原生的速度運行,計算性能要遠遠優於 JavaScript。
WebAssembly的工做流程以下(圖片來自MDN):
其中 膠水JS(JS「glue」code)的做用是提供 JS 調用 wasm 能力的接口。
編譯並測試 libwebp
咱們將 libwebp 編譯成 wasm 文件供 JavaScript 調用,提供高速解碼 WebP 的能力。具體的編譯過程能夠參照 libwebp/webp_js 的編譯說明 ,編譯環境建議使用linux/unix,其他步驟此處再也不贅述。
編譯後咱們獲得了 wasm 文件(gzip壓縮後體積51kb)和膠水 js(gzip壓縮後體積44kb) ,而後使用上述一樣的素材進行性能測試結果以下:
由以上測試基本能夠得出:
有了 WebAssembly 的加持,咱們將原有圖片加載流程進行了以下圖所示升級:
以 img 元素爲例,代碼處理邏輯以下:
// 若是當前瀏覽器環境不支持WebP格式,則使用wasm將WebP文件解碼爲Base64 if (supportsWebP) { el.src = webpSrc } else { // 使用fetch請求拿到WebP文件 const res = await fetch(webpSrc) // 設置拿到的文件的編碼,以符合wasm解碼的入參條件 const webp_data_buffer = await res.arrayBuffer() const webp_data = new Uint8Array(webp_data_buffer) // 調用碎wasm編譯生成的膠水js的解碼方法,將解碼後的Base64值做爲圖片素材的url使用 el.src = wasmDecode(webp_data) }
咱們構造了一個圖片素材較多的H5專題在 Safari 中測試,效果以下(爲了更好的體現加載過程,下放動圖相對實際速度均 放慢了3倍 ):
一、頁面元素不添加 v-webp 指令(加載圖片原文件):
二、頁面元添加 v-webp 指令(前端解碼WebP):
能夠看出在不支持WebP的宿主中,使用了 v-webp 指令後,頁面的 響應速度 (白屏時間短)和圖片 渲染速度 均有較爲明顯的提高;至此,咱們已經設計並實現了一套相對完善的圖片素材加載性能優化方案。
悟空活動中臺從提高 H5 頁面圖片加載性能的訴求出發,歷經:
等一系列手段,探索出一套基於 WebP 的圖片高性能加載方案,更好的賦能了 H5 活動的開發和運營。悟空中臺開發團隊將永不止步,持續研究和思考,爲你們帶來更多的實戰技巧,感謝您的閱讀。
【悟空活動中臺】系列往期精彩文章:
《 揭祕 vivo 如何打造千萬級 DAU 活動中臺 - 啓航篇》 主要爲你們講述 vivo 活動中臺的能力與創新。
《 悟空活動中臺 - 微組件狀態管理(上)》介紹了活動頁內 RSC 組件之間的狀態管理和背後的設計思路。
《 悟空活動中臺 - 微組件狀態管理(下)》探索平臺和跨沙箱環境下的微組件狀態管理。
《 vivo 悟空活動中臺-基於行爲預設的動態佈局方案》本文以「滿屏」場景下的頁面佈局思考爲切入點,以微組件爲元素單元,提供了一種新的佈局方案設計思路——基於行爲預設的動態佈局方案,並詳細的分享了設計目的及具體實現方案。
《 vivo悟空活動中臺 - 微組件多端探索》是基於自助多端擴展,也就意味着多端 微 組件選擇越豐富,內容越通用,玩法越多樣,產品價值也會越高。
《 悟空活動中臺 - H5 活動加載優化》從提升資源請求速度,資源壓縮、緩存、渲染等多種角度出發,尋找悟空活動專題加載優化方案。
更多內容敬請關注 vivo 互聯網技術 微信公衆號
注:轉載文章請先與微信號:Labs2020 聯繫