webp

431 阅读6分钟

背景

目前网络中图片仍然是占用流量较大的一部分,对于移动端更是如此,因此,如何在保证图片视觉不失真前提下缩小体积,对于节省带宽十分重要。

然而目前对于JPEG、PNG、GIF等常用图片格式的优化已几乎达到极致,因此Google于2010年提出了一种新的图片压缩格式 — WebP,给图片的优化提供了新的可能。

目前国内外各大互联网公司已逐步使用WebP,科技博客GigaOM曾报道,YouTube的视频缩略图采用WebP后,网页加载速度提升了10%;谷歌网上应用商店采用WebP后,每天可节省几TB的带宽,页面平均加载时间大约减少1/3;谷歌移动应用市场采用WebP图片格式后,每天节省了50TB的存储空间;2014年腾讯新闻客户端应用了WebP后,流量峰值带宽降低9GB,网络连接延时不变的前提下,平均图片延时和数据下载延时降低了100ms;2014年空间装扮也全量转换成WebP,带宽上也有显著降低。

图片格式分类

  • 无压缩。无压缩的图片格式不对图片数据进行压缩处理,能准确地呈现原图片。BMP格式就是其中之一。
  • 无损压缩。压缩算法对图片的所有的数据进行编码压缩,能在保证图片的质量的同时降低图片的尺寸。png是其中的代表。
  • 有损压缩。压缩算法不会对图片所有的数据进行编码压缩,而是在压缩的时候,去除了人眼无法识别的图片细节。因此有损压缩可以在同等图片质量的情况下大幅降低图片的尺寸。其中的代表是jpg。

gif

采用LZW压缩算法进行编码,是一种无损的基于索引色的图片格式。由于采用了无损压缩,相比古老的bmp格式,尺寸较小,而且支持透明和动画。缺点是由于gif只存储8位索引(也就是最多能表达2^8=256种颜色),色彩复杂、细节丰富的图片不适合保存为gif格式。色彩简单的logo、icon、线框图适合采用gif格式。

image.png

jpg

jpg是一种有损的基于直接色的图片格式。由于采用直接色,jpg可使用的颜色有1600w之多(2^24),而人眼识别的颜色数量大约只有1w多种,因此jpg非常适合色彩丰富图片、渐变色。jpg有损压缩移除肉眼无法识别的图片细节后,可以将图片的尺寸大幅度地减小。

但是jpg不适合icon、logo,因为相比gif/png-8,它在文件大小上丝毫没有优势

image.png

png-8

png-8采用无损压缩,是基于8位索引色的位图格式。png-8相比gif对透明的支持更好,同等质量下,尺寸也更小。非常适合作为gif的替代品。但png-8也一个明显的不足就是不支持动画。这也是png-8没办法完全替代gif的重要原因。如果没有动画需求推荐使用png-8来替代gif。

image.png

png-24

png-24采用无损压缩,是基于直接色的位图格式。png-24的图片质量堪比bmp,但是却有bmp不具备的尺寸优势。当然相比于jpg,gif,png-8,尺寸上还是要大。正是因为其高品质,无损压缩,非常适合用于源文件或需要二次编辑的图片格式的保存。

png-24与jpg一样能表达丰富的图片细节,但并不能替代jpg。图片存储为png-24比存储为jpg,文件大小至少是jpg的5倍,但在图片品质上的提升却微乎其微。所以除非对品质的要求极高,否则色彩丰富的网络图片还是推荐使用jpg。

png-24与png-8一样也支持透明

webp

WebP图片是一种新的图像格式,由Google开发。与png、jpg相比,相同的视觉体验下,WebP图像的尺寸缩小了大约30%。另外,WebP图像格式还支持有损压缩、无损压缩、透明和动画。理论上完全可以替代png、jpg、gif等图片格式,当然目前webp的还没有得到全面的支持。

看一下png图片与webp图片的对比:

image.png

如何使用转换/解压webp格式

官网跳转

如何判断浏览器支持 WebP 格式

image.png

1.谷歌官网推荐的方法

这种方法先加载一个webp格式的图片(base64字符串),如果能获取到width和height,那么是支持webp的,否则是不支持的。

//   check_webp_feature:
//   'feature' can be one of 'lossy''lossless''alpha' or 'animation'.
//   'callback(feature, isSupported)' will be passed back the detection result (in an asynchronous way!)
function check_webp_feature(feature, callback) {
    var kTestImages = {
        lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA",
        lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",
        alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",
        animation: "UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA"
    };
    var img = new Image();
    img.onload = function () {
        var result = (img.width > 0) && (img.height > 0);
        callback(feature, result);
    };
    img.onerror = function () {
        callback(feature, false);
    };
    img.src = "data:image/webp;base64," + kTestImages[feature];
}
check_webp_feature('lossy'function (feature, isSupported) {
    if (isSupported) {
        // webp is supported, 
        // you can cache the result here if you want
    }
});

2.服务端通过http请求判断

服务端通过http请求中的 Request Headers中的 accept值来判断,如果有 'image/webp'的话,返回webp格式的图片,否则返回png等格式。

image.png

3.使用canvas API

function isSupportWebp(){
  const ele = window.document.createElement('canvas')
  if (!!(ele.getContext && ele.getContext('2d'))) {
    return ele.toDataURL('image/webp').indexOf('webp')>-1
  }
  //不支持canvas,那么一定不支持webp
  return false
  }

如何运用到项目中

CDN 如何实现 WebP 兼容

无缝适配各浏览器和移动端。自动判断对于支持的客户端,响应 WebP 格式的图片;不支持的客户端,响应原图。 image.png

CDN 自动转化为 WebP 格式: image.png

  • CDN 如何判断客户端是否支持 WebP ?

该部分目前是通过 HTTP Accept 头来判断的,如果支持,则返回 WebP 副本并进行缓存;如果不支持,则返回原图。详细介绍参照上文介绍。

  • CDN 如何实现实时图片格式转换?

针对用户源站并非 WebP 格式图片的时候,CDN 层需要支持将原图图片的实时转换为 WebP 格式副本,这个在 CDN 层面是无缝支持的。这样的场景是这样的:
客户端浏览器请求一个图片资源,例如:

webp.example.com/test.png;

CDN 通过 Accept 头已经判断客户端浏览器支持 WebP 格式的图片;

用户源站取回原图并将原图实时转为 WebP 格式的图片,并响应给客户端浏览器。

前端批量转换webp格式

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

const inputPath = path.resolve('./src');
const quality = 75; // 压缩质量

function isImage(ext) {
    return ['png', 'jpg', 'jpeg'].indexOf(ext.toLowerCase()) !== -1;
}

function getWebpImgName(path) {
    return `${path}.webp`;
}

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

function handleFile(path) {
    const files = fs.readdirSync(path);

    files.forEach(function (item) {
        const filePath = path + '/' + item;

        const stat = fs.statSync(filePath);
        if (stat.isDirectory()) {
            handleFile(filePath);
        } else {
            const lastIndex = item.lastIndexOf('.');
            const ext = item.substring(lastIndex + 1);

            if (isImage(ext)) {
                console.log(filePath, 'is image');
                process.exec(getShellCmd(filePath), (err) => {
                    if (err !== null) {
                        console.log('失败');
                        console.log(err);
                    } else {
                        console.log('成功');
                    }
                });
            }
        }
    });
}
function init() {
    const isHas = fs.existsSync(inputPath);
    if (!isHas) {
        console.log('该路径找不到: ' + inputPath);
        return;
    }

    handleFile(inputPath);
}

init();
```
```