Web 页面性能优化,解决了图片相关,问题就解决了一半。本文从 Web 常见的图片格式入手,引出与图片优化相关的有效方案,期望对大家能有一点帮助。

# 背景知识

# 名词解释

  • 有损压缩:借由将次要的信息数据舍弃,牺牲一些质量来减少数据量、提高压缩比。这个过程是不可逆的。图片常见的有损压缩手段是合并相近的像素点。
  • 无损压缩:数据经过压缩后,信息不受损失,还可以完全恢复到压缩前的样子。无损压缩一般通过两个步骤完成:
    1. 产生数据的统计模型
    2. 利用这个统计模型将较常出现的数据用较短的比特序列表示较不常出现的数据用较长的比特序列表示
  • 索引色(Indexed Color):一个字节 2^8 表示一个颜色,也就是最多支持 256 种颜色
  • 直接色(Direct Color):4 个数字表示一个颜色,分别表示红、黄、蓝以及透明度,所以最多可以表达 3^32 种颜色
  • 位图(Bitmap):又叫栅格图、点阵图,使用像素阵列来表示图像。位图就是由像素阵列的排列来实现其显示效果的,每个像素有自己的颜色信息,在对位图图像进行编辑操作的时候,可操作的对象是每个像素,我们可以改变图像的色相、饱和度、明度,从而改变图像的显示效果。位图根据位深度,有 1、4、8、16、24、32 位图像等。位图放大会失真变模糊。
  • 矢量图:计算机图形学中用点、直线或者多边形等基于数学方程的几何图元表示图像。相比较位图,矢量图保存最少的信息,体积更小,缩放不会失真。
  • 通道:RGB 三种颜色信息称为红通道、绿通道、蓝通道,透明度事是 Alpha 通道。

# Web 中常用图片格式说明

  • JPEG/JPG:使用 JPEG 格式压缩的图片文件,是目前应用最广泛的图片格式之一。最普遍使用的扩展名格式为 .jpg,其他常用的扩展名还包括:.jpeg、.jpe、.jfif 以及 .jif。联合图像专家小组 1992 年发布的 JPEG 压缩标准,1994 年获得 ISO10918-1 认证。其特点是有损压缩,直接色,位图,体积最小,常用于颜色较为丰富、无透明要求的图片。
  • PNG:PNG 是一种无损压缩的位图图形格式,支持索引、会读、RGB 三种颜色方案以及 Alpha 通道等特性。PNG 开发目标是改善并取代 GIF 作为适合网络传输的格式而不需要专利许可。使用场景是带有透明、半透明背景的图片,需要在网络传输中显示预览效果后展示全貌。上古时期 IE6 不支持 PNG 半透明,需要用 hack 方法解决。PNG 体积较大,非必需可用 JPG 替代。PNG 有 png8、png24、png32 之分。
    • png8:最多支持 256 色的 PNG,无损、索引色的位图。非动图可以替代 GIF,体积较小,透明度支持也更好。
    • png24:最多支持 2^24 色的 PNG,不支持透明,无损、直接色的位图。目标是替换 JPG,但一般来说,相同显示效果,PNG 图片是 JPG 体积的数倍。
    • png32:加上 8 位 Alpha 通道的 png24.
  • GIF:图像互换格式是一种位图图形文件格式,无损压缩、索引色。原始版本为 87a,1989 年发布 89a 版本,支持多帧动画和透明色。1995 年网景 2.0 发布,定义了动画循环多少次或是否无限次播放,现在聊天的动图都是基于该版本的 GIF。GIF 的特性如下:
    • 优秀的 LZW 算法在保证质量的同时将体积变得更小
    • 可插入多帧实现动画效果
    • 可实现透明效果
    • 最多支持 256 色,故不适用于真彩色图片
  • WebP:谷歌 2010 年发布的同时支持有损和无损压缩的图片文件格式。有损用来替代 JPG,无损用来替代 PNG,动态用来替代 GIF。根据谷歌较早前的测试,WebP 的无损压缩比网络上找到的 PNG 少 45% 的文件大小,即使这些 PNG 已经使用 Pngcrush 和 PNGOUT 处理过,WebP 还是可以减少将近 30% 的文件体积。WebP 目前还不能全平台兼容,相关兼容性可在此链接 (opens new window) 查看。
  • SVG:可缩放矢量图形是一种基于可扩展编程语言(XML),用于描述二维矢量图形的图形格式。SVG 由 W3C 制定,是一个开放标准。SVG 的优点是文件可读,易于修改编辑,支持多种滤镜和特殊效果,在不改变图片文件的前提下可实现位图种类似图片阴影的效果,还可以生成动态图形。

# 图优化之请求优化

# 拥抱 HTTP2

更快、更简单、更稳定是我们每个前端工程师的追求,HTTP/2 的出现让这些美好的词汇都会聚在一起。首先来一个 demo 感受一下牛逼哄哄的 HTTP/2,HTTP/1.1 vs HTTP/2

# 二进制分帧层

HTTP/2 所有性能增强的核心在于新的二进制分帧层,它定义了如何封装 HTTP 消息并在客户端与服务端之间传输。

从这张图比较清晰地看出 HTTP/1.x 和 HTTP/2 的区别。HTTP/1.x 协议以换行符作为纯文本的分隔符,而 HTTP/2 将所有的传输的信息分割为更小的消息和帧,并采用二进制格式对它们进行编码。

# 服务端推送

一般情况下,客户端需要什么东西会告诉服务端,然后服务端返回对应的资源到客户端。HTTP/2 新增的另一个强大的功能是服务端可以对一个客户端请求发送多个响应。换句话说,服务端可以先于客户端检测到将要请求的资源,提前推送到客户端,不发送所有资源的实体,只发送资源的 URL。客户端接到后会进行验证缓存,如果发现需要这些资源,则正式发起请求。

# 标头压缩

每个 HTTP 传输都承载一组标头,这些表头说明了传输的资源以及属性。在 HTTP/1.x 中,这些元数据始终以纯文本形式,通常会给每个传输增加 500 - 800 字节的开销。如果使用 HTTP Cookie,增加的开销有时会达到上千字节。为了减少此开销和提高性能,HTTP/2 使用 HPACK 压缩格式压缩请求和响应标头元数据,这种格式采用两种简单但是强大的技术:

  • 支持通过静态霍夫曼代码对传输的标头字段进行编码,从而减小了各个传输的大小;
  • 客户端和服务端之间同时维护和更新一个包含之前见过的标头字段的索引列表(换句话说,它可以建立一个共享的压缩上下文),对相同请求而言不需要再次发送和响应,比如 User-Agent

在 HTTP/2 中,请求和响应标头字段的定义保持不变,仅有一些微小的差异:所有标头字段均为小写,请求行现在拆分为各个 :method:schema:authority:path 伪标头字段。

# 多路复用

每个 TCP 连接智能发送一个请求,HTTP/1.x 在前面的请求没有完成前,后面的请求将会阻塞。

其实在 HTTP/2 之前,我们在写代码的时候也用过类似的思想,比如:

  • 合并 CSS 和 JS 代码
  • 雪碧图
  • Iconfont

HTTP/2 的出现又让我们省掉不少麻烦。多路复用允许同时通过单一 HTTP 请求多个响应。

# 少不了的懒加载

# 什么是懒加载

只加载可视区的内容,当页面向下滚动的时候,再加载后面的内容。

图片懒加载的原理非常简单,我们先不设置图片的 src 属性,将图片的真实路径放在一个浏览器不认识的属性中(比如 data-src),然后我们去监听 scroll 事件。当页面的 scrollTop 与浏览器的高度之和大于图片剧页面顶端的 Y 时,说明图片已经进入可视区域,这时把 data-src 放到 src 中即可。

# 为什么要使用懒加载?

  • 对于大多数用户,特别是移动端和网速比较差的用户,如果首屏加载过多的图片,页面将会加载得很慢而且浪费用户的流量;
  • JavaScript 脚本通常要等到 DOM 加载完成后才会执行,如果加载的资源过多,可能会影响网页的正常使用;
  • 能够节省流量和减轻服务器压力,进一步为公司减少成本。

# 如何实现

社区成熟的轮子比比皆是:

  • Lozad.js
  • vue-lazyload
  • react-lazyload

# 正确使用缓存

缓存是一种保存资源副本并在下次请求是直接使用该副本的技术,因此使用 HTTP 缓存是 WEB 性能优化中必不可少的,也是每位前端工程师的必修课。

浏览器和服务器之间使用的缓存策略可以分为 强缓存协商缓存 两种:

  • 强缓存:在缓存数据为失效的情况下,不需要再和服务器发生交互
  • 协商缓存:需要与服务器校验是否使用缓存

# ETag

有这样一种场景,浏览器检查本地缓存找到之前响应的文件已经过期,只能去服务端请求。但是服务器的资源没有发生变化,可以说是浪费了一次请求。

ETag 的出现很好地解决了这个问题,其为一个哈希值,浏览器甚至不用去关心这个值是怎么来的。在第一次请求时,服务器对资源生成一个 ETag 并返回给浏览器,浏览器下次请求后会自动在 If-None-Match HTTP 请求头内提供 ETag,服务器根据这个哈希值与服务器本地对应资源当前的哈希进行比对。如果它未发生变化,服务器将返回 304 Not Modified 相应。这样一个来回避免了浏览器再次去请求资源,省钱又省时间。

# Cache-Control

Cache-Control 是强缓存的一种,每个资源都可以通过 Cache-Control 定义其缓存策略,Cache-Control 来控制谁可以缓存,缓存多久。

无需和服务端通信的请求是最佳的,通过本地副本消除所有网络延迟、节省流量。

指令 说明
max-age 指令指定从请求的时间开始,允许提取的响应被重用的最长时间(单位:秒)
private 只为单个用户缓存,不允许任何中间缓存对其进行缓存
no-cache 先与服务器确认返回的响应是否发生了变化,走协商缓存
no-store 禁止浏览器以及所有中间缓存存储任何版本的返回响应

# 缓存 CheckList

实际开发中往往不存在什么固定的最优解,我们需要根据不同的业务场景制定相应的策略。

  • 变与不变:一些不变的部分,如第三方库的代码,可以考虑和业务代码分离,这样一来可以减少下载资源的大小;
  • 最佳的缓存周期:不同的资源可能有不同的更新要求,设置合适的 max-age
  • ETag:当服务器上的资源未变动时无需请求

# 雪碧图

雪碧图是一种 CSS 图像合成技术,主要用于小图片显示。

# 使用场景

在网页中,为了更好的展示效果,通常会采用一些小图标来替代文字。通常的方式包括 IconFonts、SVG Icons、小图片。其中 Icon Font支持单色,SVG Icons 需 IE9+。

如不考虑兼容性问题,推荐使用 SVG Icons,体积更小,显示效果更好。

如果采用小图片,需要考虑一个问题:每张小图片独立请求,加载时都会产生一个 HTTP 请求,而小图片的体积甚至比 HTTP 请求头等结构的体积还要小,显得非常没有必要。

所以我们通常会将所有小图片合并成一张图片请求。

# 实现原理

将小图标合并成一张图片,利用 background-position 属性值来确定图片呈现的位置。

# 自动生成

每次修改或新增图片,都需要对雪碧图进行修改,如果依靠人工维护,成本太高,所以我们使用 spritesmith。它可以自动合并图片,并得到图片在合并后的相对位置:

const fs = require('fs')
const path = require('path');
const Spritesmith = require('spritesmith');

const baseDir = './images';
const files = fs.readdirSync(baseDir)
const sprites = files.map(file => path.join(baseDir, file))

Spritesmith.run({src: sprites}, (err, result) => {
  if (err) {
    console.error(err)
  } else {
    console.info(result);
  }
})

运行的输出结果如下:

{
  coordinates: {
    'images/alipay.png': { x: 0, y: 131, width: 81, height: 73 },
    'images/taobao.png': { x: 177, y: 0, width: 114, height: 114 },
    'images/wechat.png': { x: 0, y: 0, width: 177, height: 131 },
    'images/xinlang.png': { x: 81, y: 131, width: 72, height: 72 }
  },
  properties: { width: 291, height: 204 },
  image: <Buffer 89504e 470d 0a 1a 0a 0000000d 4948445200000123000000 cc 08060000003845 c5 ce 00004006494441547801 ec c1 0b 98 e6 0561 ... 22705 more bytes>
}

其中

  • coordinates:每张图片对应的尺寸和位置
  • properties:生成的图片尺寸
  • image:文件的 Buffer,可用于生成图片

对于现有的常用的构建工具,已有现成的插件可直接使用:

  • webpack-spritesmith

  • gulp.spritesmith

  • grunt-spritesmith

# 注意事项

  1. 如果要适配高清屏,需要配置高清屏的图片,并且通过设置background-size属性来使得最终显示正常,可参考以上插件的retina相关配置参数;
  2. 由于 HTTP/2 的支持多路复用特性,使得单个图片的请求的开销很小,不推荐使用雪碧图了,但为了兼容 HTTP1.1,现阶段多版本 HTTP 协议并存期间还是建议保留;
  3. 雪碧图好用,可不要滥用。

# IconFont

IconFont 叫做字体图标,即通过自体的方式展示图标,多用于渲染图标、简单图形、特殊字符等。

使用 IconFont 时,只需要引入对应的字体文件,针对加载图片张数较多的情况,可以有效减少 HTTP 请求次数,而一般字体体积较小,所以请求传输数据量较少。与直接引入图片不同,IconFont 可以像使用字体一样,设置大小和颜色,还可以通过 CSS 设置特殊样式,且因为是矢量图,不存在是真的情况。

根据开发需求,按需引入不同格式的字体文件(eot / ttf / woff/ svg)

<style>
  @font-face {
    font-family: "iconfont";
    src: url('iconfont.eot'); /* IE9*/
    src: url('iconfont.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
         url('iconfont.woff') format('woff'),
         url('iconfont.ttf') format('truetype'), /* chrome, firefox, opera, Safari,  Android, iOS 4.2+*/
         url('iconfont.svg?#iconfont') format('svg'); /* iOS 4.1- */
  }

  .iconfont {
    font-family: "iconfont";
  }
</style>

<body>
  <!-- &#xe609 是一个字符的 unicode 码,在该 iconfont 字体文件中对应某个图标 -->
  <i class="iconfont">&#xe609</i>
</body>

# Base64

Base64 是网络上最常见的用于传输 8Bit 字节码的编码方式之一,可将图片编码为特定的字符串,由 52 个大小字母和 10 个数字,以及 +、/、= 三个字符组成。

使用 Base64 编码渲染图片有以下优点:

  • 有效减少 HTTP 请求次数
  • 可对数据进行简单加密,无法肉眼获取信息
  • 没有跨域问题,无需考虑图片缓存

缺点:

  • 编码后文件体积增大,仅适用于小体积图片编码
  • 增加了编码和解码的工作量
  • 不支持 IE8 以下版本

如需将图片转换为 Base64 编码,可以使用原生 APi FileReader.readAsDataURL

# 图优化之体积优化

图片体积压缩就是在图片保持可接受的清晰度范围内同时减少文件大小,图片体积压缩可以借助工具实现。

# 在线工具

  • TinyPNG:免费,TinyPng 使用智能有损压缩技术减小 PNG 文件的文件大小,原理是将图片中相似的颜色组合起来(量化),通过减少颜色的数量,可以将 24 位 PNG 文件转为更小的 8 位索引图片,同时所有不必要的元数据被剥离。
  • PP鸭:¥69.9,很好用,根据图片的特点,选择比较兼顾大小和质量的压缩方案。
  • 智图:免费,可以选择压缩质量,生成 Webp。
  • Iloveimg:免费,支持压缩 JPG,PNG, GIF, 能保持最佳文件质量和压缩程度, 并且 Iloveimg 不仅仅是一款图片压缩工具,还支持图片的裁剪、图片的格式转换以及大小调整等功能。

# 客户端工具

  • PhotoShop:只适用 JPG,导出时选择 JPG,选择压缩质量。
  • Sketch:只适用 JPG,导出时选择 JPG,选择压缩质量。

# 阿里云 OSS

阿里云 OSS 可以通过配置参数形式对图片进行处理,支持缩放、裁剪、旋转、效果、格式转换、水印等操作,详细信息点此文档 (opens new window)查看。

示例:将图强制缩略成宽度为 100,高度为 100。

http://image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg?x-oss-process=image/resize,m_fixed,h_100,w_100

# 七牛云

七牛云的图片处理服务和阿里云 OSS 功能类似,也可以对图片进行缩放、裁剪、旋转、缩略等操作,详细文档 (opens new window)

示例:将图强制缩略成宽度为 100,高度为 100。

https://odum9helk.qnssl.com/resource/Ship.jpg?imageView2/2/w/100/h/100

# 图优化之格式、尺寸优化

# 图片格式

Web 开发中常见的图片包括 JPG、PNG、GIF、WebP,选择合适的格式以及压缩质量可以在保证视觉效果的情况下,加速网站的呈现。下面针对不同图片格式的特性来做一下对比:

类型 动画 压缩类型 浏览器支持 透明度
GIF 支持 无损压缩 所有 支持
PNG 不支持 无损压缩 所有 支持
JPEG 不支持 有损压缩 所有 不支持
webP 不支持 无损压缩或有损压缩 Chrome、Opera、Firefox、Edge、Android 支持

# 多倍图

在 Retina 视网膜屏幕面试之前人们很少关注像素密度和设备像素比,随着 Retina 屏在移动设备中越来越广泛使用,为了保证图片在不同 DPR(设备像素比)的设备上显示足够清晰,开发者需要针对不同设备适配不同倍数的图片。

# 像素相关概念

  • DP:设备像素,又名物理像素,即设备屏幕上真实的物理像素,以矩阵的形式排列。
  • DIP:设备无关像素,是一种基于屏幕坐标的抽象像素,应用程序以抽象像素为单位,如 CSS 中的单位 px,实际渲染是通过底层程序转换为物理像素。
  • DPR:设备像素比,设备像素 / 设备无关像素,在 JS 中可以通过 window.devicePixelRatio 获取。

# 多倍图概念

  • 在 DPR 为 1 的设备中,使用 1*1 个物理像素展示一个 CSS 像素
  • 在 DPR 为 2 的设备中,使用 2*2 个物理像素展示一个 CSS 像素
  • 在 DPR 为 3 的设备中,使用 3*3 个物理像素展示一个 CSS 像素

# 自适应 DPR 加载图片

在高分辨率显示屏如 2x 上,在页面中使用二倍图可以保证清晰度,但是当此页面在低 DPR 设备打开时,我们只需要 50% 长宽的图片就能保证显示效果,而此时带宽开销却是一样的。所以为了节约传输流量,我们需要告诉浏览器,根于不同的 DPR 加载不同尺寸的图片,通常有以下三种方法:

  1. 使用 picture 标签,除了 IE,现有主浏览器均已支持该标签

    <picture>
      <source srcset="photo@3x.jpg" media="(min-width: 800px)">
      <source srcset="photo@2x.jpg" media="(min-width: 600px)">
      <img srcset="photo.jpg">
    </picture>
    
  2. 使用 img srcset 属性,除了 IE,现有主浏览器均已支持该属性

    <img src="photo.png" srcset="photo@2x.png 2x, photo@3x.png 3x" alt="photo" />
    
  3. 使用 CSS3 img-set 函数,兼容性相较于前两者较差

    * {
    	background-image: image-set("photo.png" 1x, "photo@2x.png" 2x, "photo@3x.png"3x);
    }
    

# 勿忘无障碍,关怀弱视力群体

根据世界健康组织的统计,全球约有 2.85 亿视力障碍人士,仅美国就有 810 万网民患视力障碍,而在中国,这个数字要被乘以 2。不同于我们浏览网页的方式是看,视力障碍人员浏览互联网信息主要是靠听 —— 靠屏幕阅读器读出网页的有效信息,通过听这些信息来获知内容。对于前端工程师而言,关注到一些细节的优化,能更好的服务于这些视力障碍人士。

# 设置 alt 属性

最基础的方式,是装饰性图片归类到背景图,通过 CSS 背景图进行设置;功能性图片放到 HTML 中,通过 img 标签引入,且要设置 alt 属性,以便被屏幕阅读器识别并阅读。图片 alt 信息应简短,介绍图片信息即可,避免内容冗余。图片的长信息介绍应被放到 longdesc 属性中:

<img src="" alt="图片说明" />
<img src="" longdesc="一段很长的文字一段很长的文字一段很长的文字一段很长的文字" />
  • 宽或高不到 8px 或者没有 alt 的图片(特别是装饰性的图片)应该被移除,CSS 技术可以用来控制样式
  • alt 是用来描述图片的,而非单纯的新闻标题或者名词
  • alt 属性不应包含图片的文件名
  • alt 值不应包含冗长的信息(如果很长可以使用)
  • 每个 img 标签要应该设置 alt 属性
  • 不能只依赖图片上的文字信息
  • Banner、头图上的文字信息必须在代码中有所体现

# 考虑 IMG Sprite

“高对比度模式” 是一种 Windows 系统的设置主题,其用意是为了保证视力受损的用户,在查看 Web 信息时提供方便。它通过使用对比鲜明的色彩和字号来提高文本的可读性,高对比度模式下网页的背景默认会变成全黑。

CSS Image Sprites(CSS 雪碧图)是一项用来减少网页中图片 HTTP 请求数的技术,但其会导致在 Windows 高对比度模式下背景图片消失,其服务的 Web 应用性能的提升和对无障碍体验被破坏之间的矛盾,已经引起了 Web 开发者的关注。Sprite 技术的使用的确为更多网站的优化加载速度的体验贡献甚大,但我们要承认,这个过程中我们忽略并损害了使用高对比度模式用户的体验。由于 <img> 元素可以在高对比度模式下显示,故而在此场景下,使用基于 <img> 标签的 Sprite 技术,可以得到比基于 CSS 背景图的 Sprite 更多的收益。

# 总结

就前端性能优化而言,图片优化可谓是其必不可少的环节。但是与其说是在做优化,不如说是在做“权衡”。一些操作是以牺牲一部分成像质量为代价的。我们的主要任务,是尽可能的去寻求一个质量与性能之间的平衡点,并在不同业务场景下,做好图片方案的选型工作。