webp图片实践之路

最近,我们在项目中实践了webp图片,并且抽离出了工具模块,整合到了项目的基础模板中。传闻IOS10也将要支持webp,那么使用webp带来的性能提升将更加明显。估计在不久的将来,webp会成为标配。

本文主要分享一下我们在webp图片使用上的实践之路。

我们会从三部分来聊聊webp这个话题。

1.什么是webp,它有什么用?
2.使用webp的常规方法以及优劣。
3.我们是如何用上webp的。

PS:如果是对webp有一定了解的朋友,建议直接看第三部分。因为是讲我们的实践之路,所以第三部分会多讲一些。


一、什么是webp,它有什么用?


webp是谷歌推出的一种图片格式,它的优点就是同等画面质量下,体积比jpg、png这些少了25%以上。

大家都知道移动互联网时代,页面大小和用户留存息息相关,更快的加载页面才能让更多用户关注到你的内容,而图片一直都是页面体积的大头,拿我们的活动页面来说,图片占据了80%以上的页面大小。所以使用webp的话,可以瞬间让页面大小下降1/4,不得不说是一个极具性价比的优化点。

当然,它也不是没有缺点,浏览器对于webp的解码速度相对于jpg来说会慢一些,不过这和体积减小带来的性能提升,可以忽略不计了

那么既然webp这么好,为什么没有大范围使用呢?归根结底还是webp是谷歌推出的,目前主流浏览器只有chrome和安卓支持。不过IOS也快支持了,期待ing^ ^。在caniuse上可以查到webp目前的兼容性。


二、使用webp的常规方法以及它们的优劣


首先,我们需要一个工具把图片转成webp格式,这里就使用google的官方工具即可,链接。

这个装好之后,你的控制台就有了一个cwebp命令。运行cwebp -h,成功显示帮助信息就表示安装好了。

563928-20160824222352167-2066389043.png
563928-20160824222352167-2066389043.png

通过这个工具就可以生成webp图片了,有了webp图片之后,之后便是如何使用了,常见有两种方案。


方案一:服务器端处理


这是最最最省心的方法了,支持webp图片的浏览器在向服务器发送请求时,会在请求头Accept中带上image/webp。然后服务器就可以根据是否含有这个头信息来决定是否返回webp图片了

这个方法只需要在web服务器那里做一些操作即可,十分简单方便。

不过这个方案缺点也很明显,首先通过请求头检测,某些设备可能不太准。其次,现在图片等静态资源都会放到CDN服务器上,那么在这个层面加上判断webp的逻辑就有点麻烦了


方案二:前端检测是否支持webp然后再请求相应格式的图片


这个方法好处是十分稳妥,通过特性检查可以知道用户的浏览器是否支持webp,坏处就是需要在业务代码中加入检测webp的逻辑

通常做法是在页面加载前先执行一段webp的检测,得出浏览器是否支持webp格式,把结果存入cookie中,在加载图片时,如果是懒加载的图片,那么根据是否支持webp来处理图片路径就好,如果不是懒加载的图片,可以在后端渲染模板时,根据我们设置好的是否支持webp的cookie来判断。

目前这些都是针对页面通过img标签引入图片时兼容webp的方式。如果是css中引入的图片,方案一般就是构建两套css,然后在后端模板中根据cookie判断使用哪一套,或是在css中通过选择器覆盖,比如对于支持webp的浏览器,我们在html根节点上加上webps的类名,然后针对引入的图片,通过这个类名做选择器优先级覆盖,具体的我们在第三部分看着代码细说。


三、我们是如何用上webp的


重点来了,下面来说说我们对webp的实战。

首先说说我们这边现状吧,我们的图片有两种存放方式。对于一些动态图片,比如商品图,这些是存放在我们的图片服务器上,这个服务器支持webp格式,只需要在图片路径后面加上参数t=5即可得到webp格式的图片。

对于css引入的背景图,我们存放在某个CDN上,这部分就麻烦了,不支持生成webp图片,所以只能自己传一份相应的webp图片上去。

而且由于各种原因和限制,我们无法采用上述说的服务器端处理方案,所以只能采用前端代码处理的方式。我想有些公司没使用webp可能也是这些原因,因为纯前端处理确实挺绕的。

结合我们的业务情况,因为是运营活动页,背景图和商品图基本各占一半,甚至背景图更多,所以我们需要把css引入的图片和img标签引入的图片都做webp兼容T T。

针对img标签引入的图片,由于我们的图片服务器支持webp,而且我们的商品图大多是懒加载,那么就简单了,直接修改我们的懒加载插件就可以实现,在替换真实图片路径的时候判断一下是否支持webp,然后替换相应的路径就可以。

针对css引入的图片,我们采取的方案是利用css的优先级覆盖,比如说如果浏览器支持webp,那么我们给html根节点上加上webps的类名。这样比如我们写

 span{background-image:url(a.jpg)}

的时候,再写上

 .webps span{background-image:url(a.jpg.webp)}

这样,支持webp格式的设备就会自动加载webp的图片了。

当然这里你肯定会有两个疑问

一是每次写代码的时候加上.webps再写一遍工作量也太大了。

二是每张图对应的webp图片是哪里来的?需要自己生成吗?

针对这两个问题,我们找到了相应的解决方法,对于问题一我们使用css预处理器做到了生成对应的webp的代码。

问题二我们使用nodejs写了一个脚本来监控图片文件夹,当图片增加、修改、删除时,它便会生成或删除对应的webp图片。

说了这么多,我们一起来看一看代码实现吧。

  • 首先,我们需要在页面最开始的部分加入一段webp的检查代码。这段代码的作用就是检查当前浏览器是否支持webp,如果支持,那么给html根节点加上webps的类名,以供css使用。并且在cookie中记录一个名为webps,值为A的cookie,为期一年。

  • 这样,之后就可以在css中使用webp类名做兼容处理,img标签引入的图片也可以通过cookie得知浏览器是否支持webp,然后做相应处理,后端也可以通过cookie得知设备对webp的支持情况来做一些差别渲染。

这段代码如下,需要注意的是这段代码要在引入css前就加载,代码的含义可以直接看注释。

;(function(doc) {

    // 给html根节点加上webps类名
    function addRootTag() {
        doc.documentElement.className += " webps";
    }

    // 判断是否有webps=A这个cookie
    if (!/(^|;\s?)webps=A/.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 = "webps=A; max-age=31536000; domain=58.com";
                }
              };

             // 一张支持alpha透明度的webp的图片,使用base64编码
            image.src = 'data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==';
         } else {
            addRootTag();
         }
   }(document));

然后我们处理img标签引入的图片,因为我们的图片服务器支持webp,且用img引入的图片都是通过懒加载来载入的,所以这部分我们处理起来比较简单,在懒加载替换真实路径的时候,判断cookie中是否存在webps=A这个cookie来决定加载的图片的url

当然,如果你们不是懒加载的引入的图片,那么可以在后端渲染的时候,通过我们写入的cookie,来判断是否使用webp图片,也很方便。这部分代码比较简单,就不贴出来了。

然后是css中引入的图片了,由于css不支持逻辑,我们现在能利用的就是html根节点的.webps的类名了。我们在SCSS中使用了这个mixin来加载图片。代码作用可以看下注释。

/*
    通过这个函数来引入图片,例如:
    #wrapper{ @include bg('../img/sample.jpg') }
    这段代码经过编译后便会生成如下两句代码
    #wrapper{ background-image:url('../img/sample.jpg'); }
    .webp #wrapper{ background-image:url('../img/sample.jpg.webp'); }
*/
    @mixin bg($url) {
        background-image: url($url);
        @at-root(with: all) .webps & {
            background-image: url($url + '.webp');
       }
    }

如果用的是less,可以通过下面这段代码来实现同样的功能。
复制代码

.mixin(@url) {
       background-image: url(@url);
       .webps & {
           background-image: url('@{url}.webp');
    }
 }

最后就是如何生成webp图片了。对于css引入的图片,由于是放在CDN上,我们需要自己生成对应的webp图片。


如何做到开发的时候自动配套生成webp图片呢,开始我们想的是给我们的构建工具写个插件来实现编译时候生成webp图片,然而我们发现由于各个项目使用的构建工具可能不一样,比如fis3、webpack还有我们自己开发构建工具的,太多了,针对每一个开发成本太高。所以我们决定用nodejs写个小脚本,作用就是监控我们的图片文件夹,随时生成配套的webp图片,当图片有增加、修改、删除时,它会相应的增加、修改、删除对应的webp图片。


工具代码如下。默认监听images文件夹,npm install 安装依赖之后,直接node webp-monitor.js既可。当然,前提是你按照好了第二部分所说的谷歌官方的webp生成工具,或者简单的说你的终端需要有cwebp这个命令才行

    /*
      webp图片生成

      运行:npm install && npm start

      程序依赖谷歌官方webp转换工具cwebp
      mac下安装 brew install webp
      windows下可以去google官方下载

      安装完成后运行cwebp -h 如果显示了使用帮助则表示安装成功
    */

     const process = require('child_process');
     const fs = require('fs');
     const chokidar = require('chokidar');

     const log = console.log.bind(console);
     const ignoreFiles = /(^\..+)|(.+[\/\\]\..+)|(.+?\.webp$)/; // 忽略文件.开头和.webp结尾的

     let quality = 75; // webp图片质量,默认75
     let imgDir = 'images'; // 默认图片文件夹

     // 得到对应的webp格式的文件名,默认为文件名后加上.webp
     function getWebpImgName(path) {
          return `${path}.webp`;
     }

    // 得到shell命令
     function getShellCmd(path) {
          return `cwebp -q ${quality} ${path} -o ${getWebpImgName(path)}`;
      }

    // 监控文件夹
     var watcher = chokidar.watch(imgDir, {
         ignored: path => {
              return ignoreFiles.test(path);
          },
         persistent: true // 保持监听状态
     });

    // 监听增加,修改,删除文件的事件
     watcher.on('all', (event, path) => {
          switch (event) {
              case 'add':
              case 'change':
                   generateWebpImg(path, (status) => {
                       log('生成图片' + getWebpImgName(path) + status);
              });
              break;
           case 'unlink':
                deleteWebpImg(getWebpImgName(path), (status) => {
                     log('删除图片' + getWebpImgName(path) + status);
                });
               break;
           default:
               break;
         }
       });

     log('biubiubiu~~~ 监控已经启动');

     function generateWebpImg(path, cb) {
          process.exec(getShellCmd(path), err => {
              if (err !== null) {
                 cb('失败');
                 log('请先运行cwebp -h命令检查cwebp是否安装ok。')
                 log(err);
              } else {
                   cb('成功');
              }
           });
       }

      function deleteWebpImg(path, cb) {
          fs.unlink(path, (err) => {
                 if (err) {
                    cb('失败');
                    log(err)
                 } else {
                    cb('成功');
                 };
            });
        }

至此,我们便实现了在项目中使用webp图片。我们首先拿了一个活动页试水,应该是明天会上线,上线后我把地址贴出来,http://m.zhuanzhuan.58.com/Mzhuanzhuan/zhuanzhuan/zzactivity/activity-xbl/大家可以看看效果如何,经测试图片确实小了很多呢。

文章中聊到的相关代码都放到的github上,地址如下https://github.com/huangjiaxing/webp-monitor

感觉是一个比较民工的webp实践方案,对于想使用webp但是却不想和运维那些打交道的,可以尝试下这个方案,还是挺不错的^ ^。


转自:哎呦大黄 – http://www.cnblogs.com/season-huang/


PS:

1.《Firefox将支持谷歌超微型WebP图像格式》

2.《Google想通过WebP统一互联网图片格式》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,563评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,609评论 4 59
  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,629评论 1 92
  • 村口的风好大 呼呼地吹在脸上 我却感觉不到寒冷 太阳又开始落山了 妈妈打电话说 这几天要回来看我 爸爸也回来 奶奶...
    六月天气阅读 283评论 18 36
  • 舞,诱惑着原始 文·三峡刘星 这是一个小型的舞会,没有太绚丽的舞台,也没有华丽至极的音箱,但是,这适合你,你的长发...
    三峡刘星阅读 500评论 1 1