ShowJoy-com/showjoy-blog

「前端」webp图片适配流量优化

ShowJoy-com opened this issue · 1 comments

本文来自尚妆前端团队南洋

发表于尚妆博客

图片流量优化

刷新一个页面消耗的流量除了脚本样式文件以外,大头其实在下载的图片。一张图片动辄几十kb,想尽办法优化样式、脚本文件所优化的图片流量其实还不如一张图片大。

本文从两个角度介绍如何对图片流量进行优化。本文进行图片流量优化的前提都是对于移动端而言。

webp

首先从图片格式方面着手,webp(google官方网址)是谷歌推出的一种图片格式,优点在于同等画面质量下,体积比jpg、png少了25%以上。以两张jpg、png图片为例:

  1. JPG http://cdn1.showjoy.com/images/c9/c9c2221942774550ad53342da23774de.jpg

  2. 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截止20160911)

根据caniuse,目前在移动端安卓机型4.4以上全部支持,但是ios全军覆灭。我司用户ios、安卓55分成,支持了webp至少能为一半用户提供更小体积的图片体验。而且据说ios10系统将支持webp,这样一来我司产品的webp支持度将会更高。ios10有望支持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。graphicsmagick支持webp教程

图片服务器支持webp转换后,就能实时转化webp格式的图片了,为接下来的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));

如果浏览器支持webp格式的图片则在cookie中设置标志,并且在html标签上设置webpaclassName,这个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="http://cdn1.showjoy.com/images/a5/a560e106324d4670acd11b69aee0f11f.png">

3.less

.retinabg(http://cdn1.showjoy.com/images/bb/bb1c8b0d275e4ba2903dc822a03add50.png; 300x300; 150x150; png)



webpbg(http://cdn1.showjoy.com/images/bb/bb1c8b0d275e4ba2903dc822a03add50.png);

对我以上的理解有疑问和意见的欢迎找我私聊~微博-写前端的暹罗

👍