手记

如何利用Webp和http缓存节省30%的网络流量

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的支持还是很不错的,所以有必要使用起来。其实是之前无意间发现某宝和某东在使用,所以也想在这块做一些优化。

有图有真相,先看看优化后的效果吧。

使用前:

使用后:

可以很明显的看到,仅仅这八张并不是特别大的图片,便节省了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,我写了一个过滤器:

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等的缓存,以实现更好的用户体验。优化路上无止境,但愿我们都能往极致的方向去做。今天就聊到这儿,有什么说错的地方还请各位看官批评指正,希望大家多多指教!

12人推荐
随时随地看视频
慕课网APP