从 Gzip 压缩 SVG 提及 — 论如何减少资源文件的大小

从 Gzip 压缩 SVG 提及 — 论如何减少资源文件的大小

文件越小,意味着下载速度就越快。所以在向客户端发送资源文件前,使文件变得更小是件有益的事情。html

其实,精简与压缩资源文件不只是一件很棒的事情,同时也是每一位现代开发者应该尽可能去作的事情。可是,用于精简的工具一般没法作到完美精简;用于压缩的压缩器效果好坏会取决于用于压缩的数据。下面介绍一些小技巧与方法,用于调整这些工具,使其达到最好的工做状态。前端

准备工做

咱们将以一个简单的 SVG 文件为例:android

这个<svg>图像的内容为一个 10x10 像素的区域(viewBox),其中包含了两个 6x6 的正方形(<rect>)。原始文件大小为 176 字节,通过 gzip 压缩事后大小为 138 字节。ios

固然这个图像并无什么艺术感,但它足以知足这篇文章想要表达的意思,而且防止这篇文章变成长篇大论。git

第 0 步:Svgo

运行 svgo image.svg 直接进行压缩。github

(为了便于阅读,为其添加了回车与缩进)算法

能够明显地看到,rect 被替换成了 pathpath 路径形状由它的 d 属性定义,后面的一串命令相似于 canvas 的 draw 函数,控制一支虚拟的笔移动进行绘画。命令能够是绝对位移(移动 x,y),也能够是相对位移(向某方向移动 x,y)。请仔细观察其中的一条路径:canvas

M 0 0:路径起点为坐标(0, 0) h 6:水平向右移动 6 px v 6:垂直向下移动 6 px H 0:水平移动至 x = 0 z:闭合路径 — 移回路径的起点后端

这个路径画出的正方形是多么的精确!并且它比 rect 元素更加的紧凑。浏览器

另外,#f00 被改为了 red,这儿也少了一个字节!

如今文件大小为 135 字节,gzip 压缩事后为 126 字节。

第 1 步:进行总体缩放

你可能已经注意到了,两个路径中的全部坐标均为偶数。咱们是否能够把它们都除以 2 呢?

图像和以前看起来是同样的,但它缩小了两倍。所以,咱们能够对 viewBox 进行缩放,使图像与以前同样大。

如今文件大小为 133 字节,gzip 压缩事后为 124 字节。

第 2 步:使用非闭合路径

回过头来看路径。两个路径中的最后一个命令都是 z,也就是“闭合路径”。但路径在填充的时候会被隐式地闭合,所以咱们能够删除这些命令。

又少了 2 字节,如今文件大小为 131 字节,gzip 压缩事后为 122 字节。从常识上说,原始字节数越少,能压缩的大小也越小。而如今咱们已经在 svgo 以后节省了 4 个 gzip 字节了。

你可能会想:为何 svgo 不自动进行这些优化呢?缘由是缩放图像与删除尾部的 z 命令是不安全的。请看下面的例子:

这是一些有 stroke(路径宽度)的图形。从左至右分别为:原始图形、不闭合的状况、不闭合且进行缩放的状况。

线宽彻底混乱了。庆幸的是,咱们知道本身不须要使用线宽。可是 Svgo 并不知道这个状况,所以它必需要保证图形的安全,避免不安全的变换。

如今看起来不能从代码中删除任何东西了。XML 语法是严格的,如今全部的属性都是必须的,而且它们的值不能不加引号。

你觉得结束了?并不,这仅仅是个开始。

第 3 步:减小出现的字母

如今,让我来介绍一个很是方便的工具:gzthermal。它能够分析须要进行 gzip 压缩的文件,并对进行编码的原始字节进行着色。更好压缩的字节是绿色,很差压缩的数据是红色,简单明了。

请再次关注 d 属性,尤为是被标成红色的 M 命令值得注意。咱们不能删除它,但咱们能够用相对位移 m2 2 来代替它。

初始的“指针”位置为坐标轴原点(0, 0),所以移动(2, 2)和从原点移动(2, 2)是同一个意思。让咱们试试:

原始文件依然是 131 字节,可是通过 gzip 压缩事后大小仅有 121 字节了。发生了什么?答案是……

哈夫曼树(Huffman Trees)

Gzip 使用的是 DEFLATE 压缩算法,而 DEFLATE 算法是以哈夫曼树为基础构建的。

哈夫曼编码的核心思想就是使用更少的比特对出现次数更多的符号进行编码,反之亦然,出现次数不多的符号须要占用更多的比特。

没错,这儿说的是比特不是字节。DEFATE 算法会将一字节的字符视为一系列的比特,不管一字节包含 七、九、100 个比特,DEFLATE 算法都能一视同仁。

以字符串“Test”为例,根据它出现的字母来进行编码: 00 T 01 e 10 s 11 t

对每一个符号都进行过编码的字符串“Test”能够表示为:00011011,总共占 8 比特。

而后咱们把它开头的“T”改为小写“test”,再试一次: 0 t 10 e 11 s

字母 t 出现了更多的次数,它的编码也变得更短,仅为 1 比特。这个字符串通过编码后为 010110,仅为 6 比特!


在咱们的 SVG 中的 M 字母也同样。在将其变为小写以后,整个编码中都不包含大写的 M 了,能够将它从树上移除,所以平均编码长度能够更短。

当你编写对 gzip 友好的代码时,应该更多地使用那些使用频率较高的字符。即便你不能将代码长度减短,但它通过压缩后消耗的比特数也会变少。

第 4 步:回退引用(backreferences)

DEFLATE 算法还有一个特性:回退引用。某些编码点不会直接进行编码,而是告诉解码器复制一些最近解码的字节。

所以,它不须要对原始字节一次又一次地进行编码,而是能够直接引用: 向前返回 n 个字节,复制 m 个字节 例如:

Hey diddle diddle, the cat and the fiddle.

Hey diddle**<7,7>**, the cat and**<12,5>**f**<24,5>**.

巧妙的是,gzthermal 还有一种只显示回退引用的特殊模式。 gzthermal -z 会显示如下图像:

普通文本字节为橙色,可回退引用的字节为蓝色。下面的动画更直观:

除了 fill 值、m 命令和最后的 H 命令外,第二条路径几乎所有都使用了回退引用。对于 fill 和 m 咱们无能为力,由于第二个方块的确有着不一样的颜色和位置。

可是它们的形状是同样的,而且咱们如今对 gzip 有了更加清晰的认识。所以,咱们能够将绝对位移命令 H0H2 都替换为相对位移命令:h-3

如今,两个分开的回退引用合为了一个,文件大小为 133 字节,gzip 后的大小为 119 字节。虽然咱们在压缩前增长了 2 个字节,但 gzip 的结果又减小了 2 个字节!

咱们只须要关心压缩后的大小便可:在传送资源时,客户端 99.9% 用的是 gzip 或者 brotli。顺带说一下 brotli。

Brotli 压缩算法

Brotli 是于 2015 年推出的用于替换浏览器中 gzip(源自 1992)的算法。不过它与 gzip 在不少方面都有类似之处:它也是基于哈夫曼编码与回退引用的原理,所以咱们前面为 gzip 所作的调整均可以一样利于 Brotli。最后让咱们用 Brotli 应用于前面的全部步骤:

原始文件大小:106 字节 在第 0 步以后(svgo):104 字节 在第 1 步以后(viewBox):105 字节 在第 2 步以后(使用非闭合路径):113 字节 在第 3 步以后(小写 m):116 字节 在第 4 步以后(相关命令):102 字节

如你所见,最终的文件比 svgo 后的更小。这能够说明,以前咱们为 gzip 作的酷炫的工做一样适用于 Brotli。

可是,中间步骤的文件大小倒是混乱的,Brotli 压缩后的文件变得更大了。毕竟,Brotli 并非 gzip,它是一种单独的新算法。尽管与 gzip 有一些类似之处,但仍有所不一样。

其中最大的不一样是,Brotli 内置了预约义字典,在编码时使用它进行上下文启发。此外,Brotli 的最小回退引用大小为 2 字节(gzip 仅能建立 3 字节及以上的回退引用)。

能够说,Brotli 比 gzip 更加难以预测。我很想解释一下是什么致使了“压缩退化”,惋惜 Brotli 并无相似于 gzip 的 gzthermal 和 defdb 之类的工具。我只能靠它的规范 以及试错的方法来进行调试。

试错法

让咱们再试一次。此次将改变 fill 属性内的颜色。显然 red#f00 更短,但也许 Brotli 会用更长的回退引用进行压缩。

gzip 压缩事后大小为 120 字节,Brotli 压缩事后为 100 字节。gzip 流长了 1 字节,Brotli 流短了 2 字节。

此时,它在 Brotli 中表现更好,在 gzip 中表现更差。我以为,这彻底无碍!由于咱们几乎不可能一次性将数据针对全部压缩器进行优化,并获得最佳结果。解决压缩器问题就像转一个糟糕的魔方,只能尽可能优化。

总结

上面描述的全部的调整方法都不只限于 SVG 压缩为 gzip 的情景。

如下是一些能够帮助你写出更具有压缩性能的代码的准则:

  1. 压缩更小的源数据可能会获得更小的压缩数据。
  2. 不一样的字符越少就意味着熵越少。而熵越小,压缩效果就越好。
  3. 频繁出现的字符会以更小的字节被压缩。删除不常见字符以及使常见字符更常见能够提升压缩效率。
  4. 长段重复的代码能够被压缩成几个字节。DRY(“不要重复本身”原则)不必定在任何状况下都是最好的选择,有时候重复本身反而能获得更好的结果。
  5. 有些时候更大的源数据反而能够获得更小的压缩数据。减小熵可让压缩器更好地移除冗余的信息。

你能够在 此 GitHub repo 中找到以上全部资源、压缩过的图片以及其它资料。

但愿你喜欢这篇文章。下次咱们将讨论如何压缩普通 JavaScript 代码与 Webpack bundle 中的 JavaScript 代码。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索