前端常说的优化之图片优化

2,547 阅读14分钟

首先要说的是,关于图片的编码原理,压缩算法这些都是很专业的领域,作为一个小前端来讲,知道一般的应用场景和一般的特性就可以了。

基本概念

矢量图与栅格图像

  • 矢量图:形使用线、点和多边形来表示图像。
  • 栅格图像: 通过对矩形格栅内的每个像素的值进行编码来表示图像。

矢量图: 前端最常见的矢量图是 SVG。关于 SVG 可以看 SVG 基础 这篇文章,SVG 适用于包含简单的几何图形的图像,因为 SVG 缩放不失真,所以 SVG 最适合用于高分辨率屏幕和需要以不同尺寸显示的图像。

栅格图像:

  • 由一个个的像素栅格组成;
  • 每个像素都编码了颜色和透明度信息;
  • 使用不用的压缩算法(不同的图片格式),以减小图像的文件大小。

因为源数据太大,一般在网页中使用的是经过压缩的文件,下面是一个 JPG 文件的压缩和解压的过程:

jpg 编码与解码

这里涉及两个过程:

  • 编码: 源数据编码得到 JPG 文件;这个 JPG 文件,用于存储和传输。
  • 解码: 要显示这个图像的时候,解码 JPG 文件内容得到源数据。

有损压缩无损压缩

继续以上面的 JPG 编解码为例。源数据是2 维“像素”栅格,比如,一个 10 × 10 的图像便有 100 个像素。每个像素又存储了 RGBA 的值: (R) 红色通道、(G) 绿色通道、(B) 蓝色通道和 (A) alpha(透明度)通道。每个通道 8 位,即每个像素 4 个字节。 浏览器在拿到 jpg 文件之后,解码得到一个同原始数据一样的像素栅格序列,也要为每个像素分配 4 个字节的内存(无论使用什么格式传输,最后还原数据会和原始数据一样大)。

那么, 如果浏览器可以解码得到原始数据,则为无损压缩。如果为了得到更小的文件,压缩过程中丢弃了一些信息,而且这个过程是不可逆的,那么浏览器解码也就无法得到原始数据,这种压缩方式便是有损压缩

对于栅格图像要根据不同的使用场景选择合适格式,接下来探究一些常见的这几种图片格式。

常见图片格式

JPG/JPEG

可能是目前世界上使用最广泛的图像格式,特点:

  • 不支持透明;
  • 有损压缩。

JPEG 适合于色彩丰富,线条感比较弱的图片,比如大的背景图,banner 图等等。JPEG 是有损压缩(也有无损的 JPEG,支持不够广泛),压缩时质量级别越低体积越小,图片也越模糊。所以,在使用时,需要根据需要选择合适的质量级别。

优化

JPEG 有多种不同的压缩模式, 比如常见的基线(顺序)JPEG 和渐进式 JPEG (PJPEG)。

基线 JPEG(大多数图像编辑和优化工具的默认设置)以相对简单的方式进行编码和解码:从上到下。 在连接速度缓慢或不稳定的情况下加载基线 JPEG 时,用户会先看到图像的顶部,然后随着图像加载逐渐看到图像的其他部分。 盗用谷歌图:

jpg baseline

基线 JPEG 的加载方式是从上到下,而渐进式 JPEG 是从模糊到清晰。

渐进式 JPEG 将图像分成多次扫描, 第一次扫描以模糊或低质量设置显示图像,后续扫描逐步提高图像质量。 我们可以将这个过程看作“渐进式”提高图像质量。 每次“扫描”图像都会增加细节的清晰程度,最后合并为全画质图像。

jpg progressive

渐进式 JPEG 可以在没有下载完图片就可以看到最终图像的大致轮廓,一定程度上可以提升用户体验。

与基线 JPEG 相比,渐进式 JPEG:

  • 比较大的图,有更高压缩率;
  • 需要更多的 CPU 资源去解码。

使用基线 JPEG 相比还是渐进式 JPEG,需要我们根据场景选择,在文件大小,网络延迟和 CPU 消耗之间做一个权衡。


关于渐进式 JPEG 扯点别的: 渐进式 JPEG 采用的扫描算法有个学名 -- 隔行扫描,更多信息请看 维基百科 的介绍。

隔行扫描的效果是下面这样的,感觉这张图专业而且很形象:

Interlacing

除了 JPEG 可以支持隔行扫描外,PNG 和 GIF 也有支持,便是交错式 PNG\GIF。

优缺点

优点:

  • 使用高效的压缩算法,图片体积小;
  • 色彩丰富,可以支持 24bit 真彩色;
  • 压缩的质量级别可以控制。

缺点: 对于线条感较强,颜色对比强烈的图,JPG 的压缩通常会更易让人感觉到模糊;另外对于有透明度要求的图 JPG 完全无能为力,这个时候我们就要考虑 PNG 了。

PNG

特点:

  • 支持透明;
  • 除了选择调色板的大小外,PNG 不采用任何有损压缩算法。

因为 PNG 是一种无损压缩的高保真的图片格式(调色板大小的问题,下面讨论),所以,可以保存更多的细节,同时体积也就更大。

优化

既然 PNG 是无损压缩为什么一些工具还声称可以压缩 PNG 呢?比如最知名的 tinypng(也有人称为熊猫网)

看看 tinypng 官网咋说的:

How does it work?

Excellent question! When you upload a PNG (Portable Network Graphics) file, similar colors in your image are combined. This technique is called “quantization”. By reducing the number of colors, 24-bit PNG files can be converted to much smaller 8-bit indexed color images. All unnecessary metadata is stripped too. The result better PNG files with 100% support for transparency. Have your cake and eat it too!

这儿需要铺垫一下 PNG 的三种形式:PNG8,PNG24 和 PNG32。 PNG8 和 PNG24 主要的区别有:

  • 最直观的的就是颜色位数的不同。每个像素点的数据,PNG8 需要 8 位即一个字节,而 PNG24 使用 24 位也就是三个字节:RGB 三个通道各需一个字节。
  • 因为位数不同,能表示的颜色数量不同。PNG8 最多只能表示 256 色,而 PNG24 支持 24 位真彩色。
  • 压缩方式不同。PNG8 采用调色板 + 索引值的方式,PNG24 无调色板,使用点阵存储色值。
  • 半透明支持不同。PNG8 调色板中每种颜色使用 RGBA 四通道表示,支持 半透明,而 PNG24 每个像素只有 RGB 三通道,所以 PNG24 不支持半透明。

PNG24 与 PNG32:

  • PNG32 相比 PNG24 多了一个 Alpha 通道,支持半透明。
  • 很多时候不区分 PNG24 和 PNG32。比如 PS 中,只有 PNG24 的选项,如果勾选了透明选项则是表示是 PNG32。

我们回过头继续说 PNG 的优化,正如 tinypng 官网所说,他们采用的优化便是把 PNG24/PNG32 转为 PNG8。这种优化方式也称为PNG 的有损压缩

平常使用中一般选择使用 PNG8,如果 PNG8 的效果太差,则建议改为使用其他格式,因为 PNG24 会大很多。

PNG 还有一个上文提到过的优化策略 —— 交错式 PNG, 和渐进式 JPEG 的效果和原理相同,这里就不过多介绍了。

优缺点

  • 优点: 无损、质量高、支持透明。
  • 缺点: 体积大。

GIF

概述:

  • 支持动画,也支持静态 GIF;
  • 最多支持 256 色;
  • 无损压缩;
  • 支持全透明,不支持半透明。

对待 GIF 的原则一般是,除了动画外不使用 GIF。因为,对于静态 GIF 来说,PNG8 显得更加优秀:

  • 同样的调色板最多 256 色,PNG8 体积更小;
  • 同样的支持透明, PNG8 支持半透明;
  • PNG8 同样也是无损压缩(二者都可以改变调色板颜色数量来有损压缩)

但是, PNG8 也并不是总比 GIF 小,当图片小到一定尺度的时候, GIF 会比其他格式的图都会小,比如: 这样一张二维码:

png-qrcode

因为,二维码只有黑色和白色,所以把调色板的颜色数量设为 2, 导出 256 × 256 的图(单位:像素):

  • PNG8: 2.457KB
  • GIF: 3.402KB

导出为 50 × 50 的图:

  • PNG8: 648B
  • GIF: 425B

GIF 如下,还可以扫码:

gif-qrcode

而当导出更小的图时, GIF 的优势越明显。当导出尺寸为 20 × 20 的时候,二者的体积就差了一倍。

由此,也就引出了 GIF 的另外的用途: 颜色简单尺寸超小的图,可以使用 GIF,比如:

  • 一些小二维码;
  • 一些 icon 或 logo;
  • 数据上报,页面监控使用 1 × 1 px 的 GIF。

优缺点

  • 优点: 支持动画。
  • 缺点: 动画的体积大,也很消耗性能(因为 GIF 多帧之间没有压缩处理)。

WebP

WebP 是由 Google 开发的一个年轻的图像格式。同时, WebP 也是很优秀的:

  • WebP 有损文件比 JPEG 文件小 25% 至 34%;
  • WebP 无损文件比 PNG 文件小 26%;
  • 支持动画。

所以我们基本可以认为, 能使用 WebP 的时候就使用 WebP是一条很有效的优化策略。

更多信息可以看这里Google I_O 2013 - WebP(感觉视频的字幕是校对过的,很准确)。

WebP 最大的问题是兼容问题:

webp-caniuse

从图中可以看到旧浏览器和苹果家的产品基本不支持。

优化

关于 WebP 的优化,基本就是解决兼容问题。

嗅探

查看浏览器支不支持,最简单的方法就是给 img 元素一个 .webp 的图像(一般使用 1×1px 大小 webp 的 base64 字符串),看看是否能正常加载。基本原理如下:

var image = new Image();
image.onload = function() {
    // 支持
};

image.error = function() {
    // 不支持
};
image.src = 'data:image/webp;base64,xxxxx';

比如淘宝就在使用类似的技术,

webp-caniuse

<picture> 标签

HTML5 的 <picture> 给图片的各种骚操作提供了可能。

<picture>
  <source srcset="xxx.webp" type="image/webp">
  <img src="xxx.jpg" alt="">
</picture>

后端解决

Accept 请求头

对于上面的方案,仍需要前端去判别 jpg 和 webp。当然,这个判断任务可以由后端完成:

前端:

<img src="/xxx/avatar_url">

对于支持 WebP 的浏览器,在 HTTP 的请求头中会包含:

Accept: image/webp,xxxxxxx

server 端据此返回合适的资源。

User-Agent

通常我们从业务服务器只请求到了图片的 URL,而图片资源却放在了 CND 上。这种情况下可能需要业务服务器返回合适的 CDN 资源。 比如, YouTube 就是利用 AJAX 请求的 User-Agent 来判断, 对于兼容 WebP 的浏览器返回 WebP 资源 URL。

CDN

目前很多 CND 都提供了 WebP 的格式转换功能,通过参数可以指定不同的格式。与上文提到的几种方式搭配,口味更佳。比如淘宝:

chrome 中使用:

https://img.alicdn.com/xxxxxxxxx.jpg_.webp

在 IE 中使用:

https://img.alicdn.com/xxxxxxxxx.jpg

YouTube 也是类似,只不过图片的 URL 是由后端直接返回,无需前端再拼参数。

另外,有些 CND 提供自动判断功能,会根据情况返回合适的图片。这种情况下,所有的格式转换和兼容判断问题由 CDN 完成,前端和后端,都无需在做额外的工作。

优缺点

  • 优点: 支持有损压缩,无损压缩和动画,且体积更小。
  • 缺点:兼容性差,性能差(上文外链视频中数据:相对 JPEG 编码慢 5-10 倍,解码慢 1.3 倍)。

SVG

关于 SVG 基础看SVG 基础

特点:

  • 矢量图,放大不失真;
  • 纯文本文件,文件体积更小,可压缩性更强;
  • 可嵌入 HTML 中;
  • 可以使用 CSS 修改图片内容。

优化

使用 AI 和 Sketch 中导出的 SVG 包含大量元数据,例如图层信息、空属性,多余的空字、注解和 XML 命名空间等,这些在浏览器中都是不需要的。因此使用一些工具可以去掉这些没有必要的信息,比如SVGO

另外,因为 SVG 是文本文件,所以可以使用 GZIP 压缩。

对于 SVG 我所知甚少,也就知道这些了。

优缺点

  • 优点: 矢量图。
  • 缺点: 渲染成本比较高; 不适合复杂的图像。

响应式图片

如今手机屏幕的像素密度越来越高,各种设备的屏幕尺寸差别越来越大,我们需要:

  • 在不同尺寸的屏幕显示不同的图片;
  • 对于屏幕密度高的屏幕使用更清晰(尺寸更大)的图片。

srcset 和 sizes

先看看MDN 文档,如果看完文档还是有点懵逼,可以再看看这篇大神的博客

总结下基础信息:

  • srcset 和 sizes 是 <img> 元素的属性;
  • srcset: 定义了一个供浏览器选择的图像集合;
  • sizes: 必须结合 srcset 属性使用;其值也是一组值,通过不同的媒体条件,指出最合适的图片;
  • 当 srcset 使用像素密度描述符时,无需 sizes;

举个例子;

<img
     width="100px"
     src="./img/1.png"
     srcset="./img/1.png 1x, ./img/2.png 2x, ./img/3.png 3x"
/>

在 1x 的屏幕上显示 1.png; 在 2x 的屏幕上显示 2.png; 在 3x 的屏幕上显示 3.png。其中 nx 指的是 DPR -- 设备像素比 (Device Pixel Ratio)。

再来个例子:

<img src="./img/1-1.png"
    width="30%"
    sizes="(max-width: 500px) 100px, (max-width: 800px) 200px, (max-width: 1200px) 300px"
    srcset="./img/1-1.png 100w, ./img/1-2.png 200w, ./img/1-3.png 300w, ./img/1-4.png 400w"
/>

不知道如何描述,还是举例说明吧: 如果使用宽为 720px 普通 PC 浏览器(DPR=1)访问,满足 (max-width: 800px) 200px, 此时我们期望图片 CSS 尺寸为 200px,因为 DPR == 1,于是,浏览器显示一个 srcset 中 200 × 1 = 200w 的图。 如果使用 ipad(屏幕宽度 768px, DPR=2)访问,同样满足 (max-width: 800px) 200px, 同样会获得期望图片 css 尺寸 200px, 因为 DPR = 2,于是,显示一个 srcset 中 200 × 2 = 400w 的图。

<picture> 标签

<picture>
    <source srcset="./img/2-3.png" media="(min-width: 800px)" />
    <source srcset="./img/2-2.png" media="(min-width: 500px)" />
    <img width="100px" srcset="./img/2-1.png" />
</picture>

<source> 标签有 sizes 和 srcset 属性,以及例程中的 media 属性。

CSS Images

对于 CSS 中引用的图像,也可以使用响应式图片:

<style>
    .img {
        height: 100px;
        width: 100px;
        background-repeat: no-repeat;
        background-size: cover;
        /**下面是主角**/
        background-image: image-set(
            url('./img/1.png') 1x,
            url('./img/2.png') 2x,
            url('./img/3.png') 3x
        );
        background-image: -webkit-image-set(
            url('./img/1.png') 1x,
            url('./img/2.png') 2x,
            url('./img/3.png') 3x
        );
    }
</style>
<div class="img"></div>

主流的浏览器在 2013 年左右就开始就开始支持了,目前来讲兼容性还算可以,不过还需要写前缀。

其他

与图片相关的优化策略还有很多,下面简单列举一些,有机会再具体写:

  1. base64, 对于小图标 base64 后,可以直接硬编码到 HTML 中,用来减少 HTTP 请求;不过 base64 后字符串体积更大,且更消耗性能,不适用大图片。
  2. 雪碧图: 小图标的经典的解决方案,用来减少 HTTP 请求。不够灵活;图标越多越难维护。因为 Iconfont 和 SVG 相关技术的普及,雪碧图的使用越来越少。
  3. Iconfont: 矢量图标,不失真;易维护;可以用 CSS 像文本一样设置样式。
  4. SVG Sprite: 多色图标解决方案 (原理可以看SVG 基础)。
  5. 懒加载: 可以减少数据消耗,减少并发请求,改善用户体验。相关实现有很多细节,可以单独研究一下。
  6. 预加载: 使用 <link rel=preload>; 提高图片请求优先级。
  7. 缓存图片: 利用 HTTP 缓存来缓存图片资源。

最后

没有万能的优化策略,根据使用场景选择最合适的优化即可:

  • 能不使用图片就不使用,比如可以用 CSS 和 Iconfont 代替。
  • 适合用矢量图,就用矢量图。
  • 尽量使用小尺寸图片。
  • 大图尽量不使用 PNG24 / PNG32。
  • 能用 PNG8 就不用 PNG24 / PNG32。
  • 能用 WebP 就用 WebP。
  • 非动画场景不使用 GIF。
  • ......