1、图像优化
HTTP Archive上的数据显示,网站传输的数据中,60%的资源都是由各种图像文件组成的。
**图像资源优化的根本思想,可以归结为两个字:压缩。**无论是选取何种图像的文件格式,还是针对同一种格式压缩至更小的尺寸,其本质都是用更小的资源开销来完成图像的传输和展示。
1、不同设备不同分辨率图像
用于插入图像的img标签,有一个srcset属性可以用来针对不同设备,提供不同分辨率的图像文件:
除了IE和其他较低版本的浏览器不支持,目前主流的大部分浏览器都已支持img标签的srcset属性。在srcset属性中设置多种分辨率的图像文件及使用条件,浏览器在请求之前便会先对此进行解析,只选择最合适的图像文件进行下载,如果浏览器不支持,请务必在src属性中包含必要的默认图片。
使用picture标签则会在多图像文件选择时,获得更多的控制维度,比如屏幕方向、设备大小、屏幕分辨率等。
由于picture标签也是加入标准不久的元素标签,所以在使用过程中,同样应当考虑兼容性问题。
2、有损压缩和无损压缩
对于图像压缩,应该采用有损压缩还是无损压缩?如果都采用又该如何搭配设置呢?当结合了具体的业务需求再考虑后,关于压缩的技术选型就可以简单分成两步进行。
(1)首先确定业务所要展示图像的颜色阶数、图像显示的分辨率及清晰程度,当锚定了这几个参数的基准后,如果获取的图像源文件的相应参数指标过高,便可适当进行有损压缩,通过降低源文件图像质量的方法来降低图像文件大小。
如果业务所要求的图像质量较高,便可跳过有损压缩,直接进入第二步无损压缩。所以是否要进行有损压缩,其实是在理解了业务需求后的一个可选选项,而非必要的。
(2)当确定了展示图像的质量后,便可利用无损压缩技术尽可能降低图像大小。和第(1)步要通过业务决策来判断是否需要所不同的是,无损压缩是应当完成的工作环节。那么最好能通过一套完善的工程方案,自动化执行来避免烦琐的人工重复工作。
3、JPEG 压缩
JPEG包含了多种压缩模式,其中常见的有基于基线的、渐进式的。简单来说基线模式的JPEG加载顺序是自上而下的,当网络连接缓慢或不稳定时,其是从上往下逐渐加载完成的。
渐进式模式是将图像文件分为多次扫描,首先展示一个低质量模糊的图像,随着扫描到的图像信息不断增多,每次扫描过后所展示的图像清晰度也会不断提升。
通过了解两种压缩的原理不难发现,渐进式JPEG的解码速度会比基线的要慢一些,因为它增加了重复的检索开销。另外,通过渐进式JPEG压缩模式得到的图像文件也不一定是最小的,比如特别小的图像。所以是否要采用渐进式JPEG,需要综合考虑文件大小、大部分用户的设备类型与网络延迟。
渐进式JPEG的优点是显而易见的,在网络连接缓慢的情况下,首先能快速加载出一个图像质量比较模糊的预览版本。这样用户便可据此了解图像的大致内容,来决定是否继续花费时间等待完整图像的加载。这样做可以很好地提高对用户的感知性能,用户不仅知道所访问图像的大致内容,还会感知完整的图像就快加载好了。如果读者平时留心观察,应该能注意到渐进式JPEG已经在渐渐取代基线JPEG了。
1、创建渐进式 JPEG
可以通过许多第三方工具来进行处理,例如imagemin、libjpeg、imageMagick等。值得注意的是,这个步骤应当尽量交给构建工具来自动化完成,通过如下代码可以将该工作加入gulp处理管道:
出现了几种现代的JPEG编码器,比较出色的有Mozilla基金会推出的MozJPEG和Google提出的Guetzli。
MozJPEG和Guetzli也都已经有了可靠的imagemin插件支持,其使用方式与渐进式JPEG处理方式类似,这里仅列出示例代码:
MozJPEG引入了对逐行扫描的优化及一些栅格量化的功能,最多能将图像文件压缩10%,而Guetzli则是找到人眼感知上无法区分的最小体积的JPEG,那么两者的优化效果具体如何,又如何评价呢?
这里需要借助两个指标来进行衡量,首先是用来计算两个图像相似度的结构相似性分数(Structural Similarity index),简称SSIM,具体的计算过程可以借助第三方工具jpeg-compress来进行,这个指标分数以原图为标准来判断测试图片与原图的相似度,数值越接近1表示和原图越相似。
Butteraugli则是一种基于人类感知测量图像的差异模型,它能在人眼几乎看不出明显差异的地方,给出可靠的差别分数。如果SSIM是对图像差别的汇总,那么Butteraugli则可以帮助找出非常糟糕的部分。
不仅要考虑图像压缩的质量和保真度,还要关注压缩后的大小,没有哪种压缩编码方式在各种条件下都是最优的,需要结合实际业务进行选择。这里可以给读者一些使用建议:
●使用一些外部工具找到图像的最佳表现质量后,再用MozJPEG进行编码压缩。
●Guetzli会获得更高质量的图像,压缩速度相对较慢。
在对JPEG进行编码优化时,应主要关注业务可接受的最低图像质量。
4、GIF
GIF是Graphics Interchange Format的缩写。
GIF 优化的几个点:
1、单帧的 GIF 转化为 PNG
首先可以使用npm引入ImageMagick工具来检查GIF图像文件,看其中是否包含多帧动画。如果GIF图像文件中不包含多帧动画,则会返回一个GIF字符串,如果GIF图像文件中包含动画内容,则会返回多帧信息。
对于单帧图像的情况,同样可使用ImageMagick工具将其转化为更适合展示图形的PNG文件格式。对于动画的处理稍后会进一步介绍,这里先列出代码示例:
2、GIF 动画优化
由于动画包含了许多静态帧,并且每个静态帧图像上的内容在相邻的不同帧上通常不会有太多的差异,所以可通过工具来移除动画里连续帧中重复的像素信息。这里可借助gifsicle来实现:
3、用视频替换动画
建议将内容较长的GIF动画转化为视频后进行插入,因为动画也是视频的一种,成熟的视频编码格式可以让传输的动画内容节省网络带宽开销。
可以利用ffmpeg将原本的GIF文件转化为MPEG-4或WebM的视频文件格式。
相比视频文件,GIF在解码阶段也是十分耗时的,所以出于对性能的考虑,在使用GIF前应尽量谨慎选择。
5、PNG
相比于JPEG,PNG支持透明度,对线条的处理更加细腻,并增强了色彩的表现力,不过唯一的不足就是文件体积太大。如果说GIF是专门为图标图形设计的图像文件格式,JPEG是专门为照片设计的图像文件格式,那么PNG对这两种类型的图像都能支持。通常在使用中会碰到PNG的几种子类型,有PNG-8、PNG-24、PNG-32等。
1、对比 GIF
PNG-8也称为调色板PNG,除了不支持动画,其他所有GIF拥有的功能它都拥有,同时还支持完全的alpha通道透明。只要不是颜色数特别少的图像,PNG-8的压缩比表现都会更高一筹。
在使用单帧图形图像时,应当尽量用PNG-8格式来替换GIF格式。
2、对比 JPEG
当所处理图像中的颜色超过256种时,就需要用到JPEG或者真彩PNG,真彩PNG包括PNG-24和PNG-32,二者的区别是真彩PNG-24不包括alpha透明通道,而加上8位的alpha透明通道就是真彩PNG-32。
JPEG是有损的,它拥有更高的压缩比,也是照片存储的实际标准,如果还是要用PNG,那么很可能是在清晰的颜色过度周围出现了不可接受的"大色块"。
3、优化 PNG
PNG图像格式还有一个优点,就是便于扩展,它将图像的信息保存在"块"中,开发者便可以通过添加一些自定义的"块"来实现额外的功能,但所添加的自定义功能并非所有软件都能读取识别,大部分可能只是特定的作图软件在读取时使用而已。
因此我们可以使用pngcrush对这些多余的块进行删除压缩,通过npm引入imagemin-pngcrush,代码如下:
其中,imageminPngcrush()中可以带入如下一些参数进行压缩控制:
●-rem alla:删除所有块,保留控制alpha透明通道的块。
●-brute:采用多种方法进行压缩会得到较好的压缩效果,由于执行的方法较多,所以执行压缩的速度会变慢,建议在离线操作下可以添加,但有时改进效果并不明显,如果对构建流程有要求可不加。
●-reduce:会尝试减少调色板使用的颜色数量。
6、WebP
GIF虽然包含的颜色阶数少,但能呈现动画;JPEG虽然不支持透明度,但图像文件的压缩比高;PNG虽然文件尺寸较大,但支持透明且色彩表现力强。开发者在使用位图时对于这样的现状就需要先考虑选型。假如有一个统一的图像文件格式,具有之前格式的所有优点,WebP就在这样的期待中诞生了。
WebP是Google在2010年推出的一种图像文件格式,它的目标是以较高的视觉体验为前提的,尽可能地降低有损压缩和无损压缩后的文件尺寸,同时还要支持透明度与动画。根据WebP官方给出的实验数据,当使用WebP有损文件时,文件尺寸会比JPEG小25%~34%,而使用WebP无损文件时,文件尺寸会比PNG小26%。
使用构建工具辅助完成,比如通过npm安装webp-loader后,在webpack中进行如下配置:
使用时考虑 WebP 的浏览器兼容性即可。
7、SVG
SVG这种基于XML语法描述图像形状的文件格式,就适合用来表示Logo等图标图像。
1、SVG 的优化建议
(1)应保持SVG尽量精简,去除编辑器创建SVG时可能携带的一些冗余信息,比如注释、隐藏图层及元数据等。
(2)由于显示器的本质依然是元素点构成位图,所以在渲染绘制矢量图时,就会比位图的显示多一步光栅化的过程。为了使浏览器解析渲染的过程更快,建议使用预定义的SVG形状来代替自定义路径,这样会减少最终生成图像所包含标记的数量,预定义形状有〈circle〉、〈rect〉、〈line〉、〈polygon〉等。
(3)如果必须使用自定义路径,那么也尽量少用曲线。
(4)不要在SVG中嵌入位图。
(5)使用工具优化SVG,这里介绍一款基于node.js的优化工具svgo,它可以通过降低定义中的数字精度来缩小文件的尺寸。通过npm install -g svgo可直接安装命令号方式使用,若想用webpack进行工程化集成,可加入svgo-loader的相关配置:
其中,可在svgo-config.yml的配置文件中定义相关优化选项:
(6)在优化过后,使用gzip压缩和(或)brotli压缩。
8、Base64
准确地说,Base64并不是一种图像文件格式,而是一种用于传输8位字节码的编码方式,它通过将代表图像的编码直接写入HTML或CSS中来实现图像的展示。一般展示图像的方法都是通过将图像文件的URL值传给img标签的src属性,当HTML解析到img标签时,便会发起对该图像URL的网络请求。
当采用Base64编码图像时,写入src的属性值不是URL值,而是类似下面的编码:
浏览器会自动解析该编码并展示出图像,而无须发起任何关于该图像的URL,这是Base64的优点,同时也隐含了对于使用的限制。由于Base64编码原理的特点,一般经过Base64编码后的图像大小,会膨胀四分之三。
这对想尝试通过Base64方式尽可能减少HTTP请求次数来说是得不偿失的,较复杂的大图经过编码后,所节省的几次HTTP请求,与图像文件大小增加所带来的带宽消耗相比简直是杯水车薪。所以也只有对小图而言,Base64才能体现出真正的性能优势。
考虑是否使用Base64编码时,比对如下几个条件:
●图像文件的实际尺寸是否很小。
●图像文件是否真的无法以雪碧图的形式进行引入。
●图像文件的更新频率是否很低,以避免在使用Base64时,增加不必要的维护成本。
9、格式选择建议
图像文件使用策略
根据性能选择:
1、首选 SVG
2、其次使用 WebP
3、包含动画则使用 GIF,时间较长/文件较大的 GIF 推荐转为视频
4、不需要更高的图像细节时,选择 JPEG
5、需要更高的图像细节且包含高色阶,使用 PNG-24
6、需要更高的图像细节且不包含高色阶,使用 PNG-8
根据类型选择:
考虑到矢量图具有缩放不失真且表示图标时较小的文件尺寸,凡用到图标的场景应尽量使用矢量图;
而对于位图的使用场景,由于在相同图像质量下其具有更高的压缩比且支持动画,所以WebP格式应该是我们的首选。
10、使用建议
1、CSS Sprite
CSS Sprite技术就是我们常说的雪碧图,通过将多张小图标拼接成一张大图,有效地减少HTTP请求数量以达到加速显示内容的技术。
通常对于雪碧图的使用场景应当满足以下条件:
首先这些图标不会随用户信息的变化而变化,它们属于网站通用的静态图标;
同时单张图标体积要尽量小,这样经过拼接后其性能的提升才会比较乐观;若加载量比较大则效果会更好。
雪碧图的使用方式也很简单,通过CSS的background-image属性引入雪碧图的URL后,再使用background-position定位所需要的单个图标在雪碧图上的起始位置,配合width和height属性来锁定具体图标的尺寸,示例代码如下:
2、Web 字体
使用Web字体有多种优点:增强网站的设计感、可读性,同时还能搜索和选取所表示的文本内容,且不受屏幕尺寸与分辨率的影响,能提供一致的视觉体验。除此之外,由于每个字型都是特定的矢量图标,所以可以将项目中用到的矢量图标打包到一个Web字体文件中使用,以节省对图标资源的HTTP请求次数,这样做类似雪碧图优化目的。
目前网络上常用的字体格式有:EOT、TTF、WOFF与WOFF2,由于存在兼容性的问题,并没有哪一种字体能够适用所有浏览器,所以在实际使用中,网站开发者会声明提供字体的多种文件格式,来达到一致性的体验效果。在Web项目中,一般会先通过@font-face声明使用的字体系列:
1、子集内嵌
通过@font-face和unicode-range属性就可以定义所使用的字体子集,属性unicoderange用来指定所需字体在@font-face声明字体集中的子集范围,它支持三种形式:单一取值(如U+233)、范围取值(如U+233-2ff)、通配符范围(如U+2??),取值的含义是字体集文件中的代码索引点,具体使用示例如下:
通过使用子集内嵌,以及为字体的不同样式变体采用单独的文件,用户可以仅根据需要下载字体的子集,而不必强制他们下载可能永远都不会用到的字体子集,这样对字体下载优化来说会更快速高效。不过属性unicode-range也存在兼容性的问题,对于不支持的浏览器,若想提供必要的子集字体支持,则可能需要手动处理字体文件。
2、字体文件预加载
在默认情况下,构建渲染树之前会阻塞字体文件的请求,这将可能导致部分文本渲染延迟,对此我们可使用〈link rel="preload"〉对字体资源进行预加载。
〈link rel="preload"〉需要和@font-face对字体的定义一同使用,它只负责提示浏览器需要预加载给定的资源,而不指明如何使用。但同时需要注意的是,这样做将会无条件向网络发出字体请求,如果项目迭代将原本使用的字体文件修改或删除,也需同步删除对字体预加载的设置。
3、注意 display:none 的使用
下面img1.jpg的图像文件是否有被浏览器发起请求?即使父级的div设置为不显示。
根据HTML的解析顺序,答案是肯定的,img1.jpg的图像文件会被请求。那么下面img2.jpg的图像文件会发起请求吗?
CSS解析后发现父级使用了display:none,再去计算子级的样式就没有多大意义了,所以就不会去下载子级div的背景图像。
推荐的做法是使用〈picture〉或〈img srcset〉的方式进行响应式显示。