webp 圖片適配流量優化
圖片流量優化
刷新一個頁面消耗的流量除了腳本樣式文件以外,大頭其實在下載的圖片。一張圖片動輒幾十kb,想盡辦法優化樣式、腳本文件所優化的圖片流量其實還不如一張圖片大。
本文從兩個角度介紹如何對圖片流量進行優化。本文進行圖片流量優化的前提都是對于 移動端 而言。
webp
首先從圖片格式方面著手,webp( google官方網址 )是谷歌推出的一種圖片格式,優點在于同等畫面質量下,體積比jpg、png少了25%以上。以兩張jpg、png圖片為例:
-
JPG http://cdn1.showjoy.com/images/c9/c9c2221942774550ad53342da23774de.jpg
-
PNG http://cdn1.showjoy.com/images/bb/bb1c8b0d275e4ba2903dc822a03add50.png
size | JPG | PNG |
---|---|---|
無壓縮 | 165kb | 55kb |
tinypng壓縮 | 75kb | 20kb |
webp轉換 | 54kb | 6.1kb |
由表格的羅列可知,將圖片轉換為webp格式,圖片的體積比tinypng壓縮完后的體積還要小,且圖片質量甚至還要高于tinypng壓縮。
雖然webp格式的圖片相對于png和jpg體積小質量高,但是目前的兼容性在全球范圍只達到了70%左右。
根據caniuse,目前在移動端安卓機型4.4以上全部支持,但是ios全軍覆滅。我司用戶ios、安卓55分成,支持了webp至少能為一半用戶提供更小體積的圖片體驗。而且據說ios10系統將支持webp,這樣一來我司產品的webp支持度將會更高。
圖片服務器支持webp轉換
我司原本就有基于 nginx+lua+graphicsmagick 的圖片縮略圖功能,具體使用方法類似
http://cdn1.shwojoy.com/images/34/xxxxx.png
http://cdn1.shwojoy.com/images/34/xxxxx.png.300x300.png
為了支持webp轉換需要修改原先的lua腳本,添加對 .webp 后綴的識別。使之能對類似 xxxxx.png.300x300.png.webp 或者 xxxxx.png.webp 這樣的域名進行識別并轉換。
nginx+lua+graphicsmagick 這套方案其實做的事情就是 nginx 對域名進行攔截, lua 腳本進行域名后綴規則的匹配,比如說 300x300.png / .webp 類似的后綴,匹配完成后再在lua里調用 graphicsmagick 的命令,進行一些圖片轉換、裁剪等工作。
lua腳本片段
if table.isLegal(size_list) and extend == "webp" then
command = [[/usr/local/GraphicsMagick-1.3.25/bin/gm convert -quality 75 -density 72 +profile "*" ]] .. ngx.var.image_root .. originalUri .. " -geometry " .. area .. " " .. ngx.var.file;
os.execute(command);
end
值得注意的是graphicsmagick版本在1.3.20及以上才支持webp download
graphicsmagick能做到轉換webp還需要下載編譯libwebp。
圖片服務器支持webp轉換后,就能實時轉化webp格式的圖片了,為接下來的webp兼容方案提供了技術支持。
- http://cdn1.showjoy.com/images/c9/c9c2221942774550ad53342da23774de.jpg
- http://cdn1.showjoy.com/images/c9/c9c2221942774550ad53342da23774de.jpg.webp
webp兼容方案
目前在瀏覽器端判斷是否支持webp最好的方法就是特性檢測法。根據檢測結果將是否支持webp的值存入cookie,供之后需要判斷webp兼容性的地方使用。
特性檢測腳本:
;(function(doc) {
// 給html根節點加上webps類名
function addRootTag() {
doc.documentElement.className += " webpa";
}
// 判斷是否有webp_showjoy=available這個cookie
if (!/webp_showjoy=available/.test(document.cookie)) {
var image = new Image();
// 圖片加載完成時候的操作
image.onload = function() {
// 圖片加載成功且寬度為1,那么就代表支持webp了,因為這張base64圖是webp格式。如果不支持會觸發image.error方法
if (image.width == 1) {
// html根節點添加class,并且埋入cookie
addRootTag();
document.cookie = "webp_showjoy=available; max-age=31536000; domain=";
}
};
// 一張支持alpha透明度的webp的圖片,使用base64編碼
image.src = 'data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==';
} else {
addRootTag();
}
}(document));</code></pre>
如果瀏覽器支持webp格式的圖片則在cookie中設置標志,并且在html標簽上設置 webpa className,這個classname的作用是在less文件中兼容webp圖片。
less文件中的webp兼容
.webpbg(@url) {
background-image: url(@url);
.webpa & {
background-image: url('@{url}.webp');
}
}
這段less取代了原先在less文件中描述背景圖片的代碼。
比如原先定義背景圖片
div {
background-image: url(xx);
}
現在使用兼容方案則為:
div {
webpbg(xxx);
}
html文件中的webp圖片兼容
我司目前html部分通過java的velocity編寫,對于同步傳遞到頁面上的圖片webp將通過如下方式兼容。
<img class="slider-img" src="$!{banner.recordMap.get('圖片地址').value}.750x448.jpg$!{isWebp}">
$!{isWebp} 變量是后臺通過判斷瀏覽器請求的cookie中是否有之前定義的 webp_showjoy=available ,有則返回 .webp 后綴,無則返回空。
這樣有個問題是用戶第一次瀏覽產品頁面時后臺判斷cookie永遠是false,所以用戶第一次瀏覽是不可能返回 .webp 后綴的圖片的。
還有一種情況是圖片大量使用的時候我們會使用懶加載進行圖片的延遲加載。這時就可以修改懶加載插件,在插件里動態兼容webp圖片了。
/* 根據cookie返回圖片是否webp的地址 */
function getwebpsrc (imgsrc) {
var needwebp = false,
src = '';
if (/webp_showjoy=available/.test(document.cookie)) {
needwebp = true;
}
src = needwebp ? imgsrc + '.webp' : imgsrc;
return src;
}
到此就完成了移動端對webp格式圖片的支持。這也是圖片流量優化其中之一方案。
retina兼容圖片流量優化
前端應該都了解在retina屏下應該使用 @2x 或者 @3x 等倍率的圖片,才能保證圖片的清晰度。但是為了切圖方便,部分公司都會統一在切圖階段切出 @2x 的圖片,不管瀏覽設備是retina屏還是普通屏,一律都使用 @2x 的圖片。這樣做有兩個壞處,一是 @2x 的圖片在非retina屏下會出現downsampled現象,雖然不會影響清晰度,但是會缺少一些銳利度。二是 @2x 的圖片相比較 @1x 的圖片,前者體積大于后者,這也就造成了流量的浪費以及影響頁面打開性能。
所以正確處理方法是針對retina屏的是否采用不同尺寸的圖片。圖片裁剪已經在圖片服務器上實現。在考慮retina時也需要加上webp的兼容,兩者一起作用會大大減少圖片的尺寸。
@2x http://cdn1.showjoy.com/images/bb/bb1c8b0d275e4ba2903dc822a03add50.png.300x300.png.webp
@1x http://cdn1.showjoy.com/images/bb/bb1c8b0d275e4ba2903dc822a03add50.png.150x150.png.webp
less文件兼容retina
.retinabg(@file-2x; @reg-2x; @reg-1x; @type) when (isstring(@reg-2x)) {
background-image: url("@{file-2x}.@{reg-1x}.@{type}");
.webpa & {
background-image: url('@{file-2x}.@{reg-1x}.@{type}.webp');
}
@media
only screen and (-webkit-min-device-pixel-ratio: 2),
only screen and ( min--moz-device-pixel-ratio: 2),
only screen and ( -o-min-device-pixel-ratio: 2/1),
only screen and ( min-device-pixel-ratio: 2),
only screen and ( min-resolution: 192dpi),
only screen and ( min-resolution: 2dppx) {
background-image: url("@{file-2x}.@{reg-2x}.@{type}");
.webpa & {
background-image: url('@{file-2x}.@{reg-2x}.@{type}.webp');
}
}
}
當要代替原先書寫時的 background-image: url() ,可以寫成如下方式:
.retinabg(http://cdn1.showjoy.com/images/bb/bb1c8b0d275e4ba2903dc822a03add50.png; 300x300; 150x150; png)
html文件兼容retina
在html中本次方案準備使用html5特性 srcset 屬性。
secset 屬性的目的在于允許開發者為某個圖片的屬性指定一系列的來源,其中這些圖片的來源是要根據客戶端顯示屏的像素分辨率來設定的。
比如在volecity模板中定義圖片資源:
<img class="pic" src="$!{newProduct.image}.300x300.png$!{isWebp}" srcset="$!{newProduct.image}.150x150.png$!{isWebp} 1x, $!{newProduct.image}.300x300.png$!{isWebp} 2x">
這段圖片定義說明了在retina(2x)屏幕下使用300x300的圖片來源,而在飛retina屏下(1x)下使用150x150的圖。
html中懶加載的圖片
由于懶加載的圖片在插件中就會進行src賦值的操作,所以直接在懶加載插件中根據 window.devicePixelRatio 進行判斷修改圖片url。
/* 根據cookie返回圖片是否webp的地址 */
/* 根據dpr返回不同尺寸的圖片 */
function getwebpsrc (imgsrc) {
var areaInfo = '';
if (window.devicePixelRatio && window.devicePixelRatio <= 1) {
var area = imgsrc.match(/[0-9]+x[0-9]+/);
if (area) {
var areaSplit = area[0].split('x');
areaInfo = areaSplit[0] /2 + 'x' + areaSplit[1] /2;
imgsrc = imgsrc.replace(/[0-9]+x[0-9]+/, areaInfo)
}
}
var needwebp = false,
src = '';
if (/webp_showjoy=available/.test(document.cookie)) {
needwebp = true;
}
src = needwebp ? imgsrc + '.webp' : imgsrc;
return src;
}
在這個方案下所有圖片的編寫都必須要帶上尺寸后綴(_num_x_num_),在我司圖片服務器支持下還有一個特別的好處:圖片服務器會對帶有尺寸后綴的圖片進行尺寸裁剪的同時進行圖片壓縮,遇到不支持webp格式的瀏覽器上就會順帶對圖片進行壓縮,盡力地減小圖片體積。

srcset 屬性目前在移動端的兼容性十分不錯,安卓4.x版本不支持。
總結
一共兩點圖片流量優化方案,一是針對webp圖片格式,二是針對retina。最后總結下來的編寫圖片代碼的情景就為3種:
1.html同步圖片編寫
<img class="pic" src="$!{newProduct.image}.300x300.png$!{isWebp}" srcset="$!{newProduct.image}.150x150.png$!{isWebp} 1x, $!{newProduct.image}.300x300.png$!{isWebp} 2x">
2.懶加載圖片編寫
<img class="goods-pic j_Lazyload" data-original="{{$value.image}}.300x300.png" src="https://simg.open-open.com/show/a3b244c260fdb7164e263238fad130a7.png">
3.less
.retinabg(http://cdn1.showjoy.com/images/bb/bb1c8b0d275e4ba2903dc822a03add50.png; 300x300; 150x150; png)
webpbg(http://cdn1.showjoy.com/images/bb/bb1c8b0d275e4ba2903dc822a03add50.png);
對我以上的理解有疑問和意見的歡迎找我私聊~微博-寫前端的暹羅
來自:https://github.com/ShowJoy-com/showjoy-blog/issues/10