從 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前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索