精度を保ってSVGを1バイトでも小さくする

# あらまし

SVGを利用するメリットのひとつは、CSSにインラインで埋め込めるほどのサイズの小ささです。

ただ、SVGは作り方を誤るといくらでもファイルサイズが大きくなるデータフォーマットでもあります。

CSSのインラインに埋め込むということは、同時にそのWebページで表示されることのないSVGもロードされてしまいがち。エンジニアとしては、ちゃんと最低限のサイズで埋め込みをすることで通信コストを抑え、CO2排出量削減に貢献したいですね！

ちなみに、ここではSVGはHTMLに埋め込めればいいや、ではなく、SVG-valid (XML-valid) なサイズ削減を目指しています。

# チェック項目

## 軽量化する前に

SVGはどう利用されるのか（どのくらいのサイズで描画されるのか、白黒でいいのか、色を塗り分けたいのか、JavaScriptで動的に制御したいのか、など）によって、パスをどう定義するべきかが変わってきます。

**そのSVGファイルは誰がどこでどのように利用するのか**を初めに把握しておく必要があります。

 - 座標の指定はどのくらいの精度が必要なのか
 - （あとで動的に色を塗り分けたいなどの理由で）パスを分割しておくべきか
 - （あとで動的に描画したいなどの理由で）パスの定義の順番を正しく保持するべきか

## 不要な宣言がなされていないか確認する

IllustratorやSketchなどのドロワーソフトで書き出すと、メタデータやレイヤーの情報などが含まれている場合があります。この辺りは削除してしまいましょう。 

XML宣言や文書型宣言は削除できる場合があります。

`svg` タグの `width`, `height` の見直しも行いたいところ。

多くの場合、

```xml
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300">
  ...
</svg>
```

のように、 `viewBox` が `0 0 x y` で定義されていることが多いと思いますが、この場合、 `width`, `height` の指定は不要です。つまり、下記と同義です。

```xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300">
  ...
</svg>
```

多くの場合、これが最小限の記述です。これ以外の記述があれば、それは削除可能なものかもしれません。

HTMLに埋め込むことだけを考えると、  `xmlns="http://www.w3.org/2000/svg"` も削除できますが、SVG-validでなくなってしまうので、ここでは削除しないこととします。

## CSSを確認

例えば、一度しか使われないにもかかわらず `class` で指定していたりすると冗長です。

また、CSSを利用する場合も、CSS自体もできるだけ小さくしましょう。

 - `class` 名は1文字に
 - （場合にもよりますが）`class` より `id` を使う
 - 色は `#xxx` やより短いキーワード（`red`など）に置き換える
 - 余計なスペースは入れない
 - 最後の定義の後ろに `;` は入れない
 - 0から1の小数は `0` を書かない

実践すると、こんな感じ。

```CSS
.hoge { fill-rule: evenodd; fill: #ff0000; opacity: 0.5; }
```

```CSS
#a{fill-rule:evenodd;fill:red;opacity:.5}
```

## 無駄な座標を定義していないか確認

別のパスで塗りつぶされていて見えない点が定義されていないか確認しましょう。

![影がついた星](https://qiita-image-store.s3.amazonaws.com/0/50013/c95fb046-7d21-9e08-8710-7d22e969399b.png)
例えばこういう影がついた図形を

```xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
  <path d="M27,5 29,12 37,12 32,17 33,25 27,20 21,25 23,17 17,12 25,12Z" fill="black"/>
  <path d="M25,4 27,11 35,11 29,16 31,24 25,19 19,24 21,16 15,11 23,11Z" fill="red"/>
</svg>
```

こういう風に、影の図形まで全ての座標を定義してあげる必要はありません。隠れている左上の座標（`17,12`）は削除できます。

## 要素の閉じタグを書いていないか

下記は同義です。

```xml
<path d="..."></path>
```

```xml
<path d="..."/>
```

## `line`, `polyline`, `polygon` 要素を使っていないか確認

これらの要素は `path` 要素に置き換えが可能です。置き換えた方がサイズが小さくなります。

## `path` 要素を使いこなす

`path` では省略できる記述がいくつもあります。積極的に使っていきましょう。

その前に、 `path` を正しく理解する必要があります。下記サイトがとてもわかりやすいです。

http://defghi1977.html.xdomain.jp/tech/svgMemo/svgMemo_03.htm

### 不要なスペースは削除

`d` 属性内では、スペースか `,` がセパレータとして利用されますが、明らかにそこにセパレータが入っているとわかる場合は、セパレータを省略できます。

```xml
<path d="M10,10 l10,10 l0,-10"/>
```

まず、 `l` コマンドの前のスペースはなくても意味が通ります。

```xml
<path d="M10,10l10,10l0,-10"/>
```

さらに、 `-10` の前の `,` はなくても、そこが別の値を定義していることがわかるので省略できます。

```xml
<path d="M10,10l10,10l0-10"/>
```

### `H`, `h`, `V`, `v` に置き換えられるケースがある

水平線、垂直線の場合、 `H`, `h`, `V`, `v` コマンドを利用できます。

下記は同義です。

```xml
<path d="M10,10 l10,10 l0,-10"/>
```

```xml
<path d="M10,10 l10,10 v-10"/>
```

### `Z`, `z` は省略できるケースがある

直前の初期位置（`M`, `m`）まで直線を引く `Z`, `z` コマンドですが、`stroke` せずに `fill` だけする場合、省略しても同じように領域が塗りつぶされます。

### 連続したコマンドは省略できる

`path` では連続したコマンドは省略できます。

```xml
<path d="M10,10l10,10l0,10"/>
```

```xml
<path d="M10,10l10,10 0,10"/>
```

ただし、セパレータが必要なため、省略したコマンドの次が0以上の値の場合は意味がありません。上述した通り `-` によってセパレータが省略可能な場合に意味があります。

```xml
<path d="M10,10l10,10l-10,10"/>
```

```xml
<path d="M10,10l10,10-10,10"/>
```

### `M` の後の `L` と `m` の後の `l` は省略できる

これも同様に、負の値が設定される場合に効果があります。

```xml
<path d="m10,10l-10,0"/>
```

```xml
<path d="m10,10-10,0"/>
```

### 図形を描画する方向を考える

このように、コマンド後が負の値から始まる方が文字を省略できることがあるので、図形を時計回りで描画するか、反時計回りで描画するかで、微妙にサイズが違ってきます。

上で描いた星（影なし）を、相対座標で指定してみます。

時計回り

```xml
<path d="M25,4l-2,7h-8l6,5-2,8 6-5 6,5-2-8 6-5h-8"/>
```

反時計回り

```xml
<path d="M25,4l2,7h8l-6,5 2,8-6-5-6,5 2-8-6-5h8"/>
```

### 基準点（描き始め）をどこにするか考える

上述のように、

 - 連続するコマンドは省略できる
 - 直線で閉じ、かつ、 `stroke` しない場合は `z` を省略できる

ので、パスの描き始めは

 - 最後のコマンドは、直前のコマンドと同じコマンド
 - 最後は直線で閉じられるところ

にしたほうが良いですね。

### 絶対座標、相対座標を使いこなす

コマンドの大文字、小文字それぞれ絶対座標、相対座標での指定が可能ですが、どちらで指定した方がより短くなるか考えましょう。

100x100 の `viewBox` であるとして、周囲5だけマージンを開けた90x90の正方形を、絶対座標、相対座標でそれぞれ書いてみます。

```xml
<path d="M5,5H95V95H5"/>
```
```xml
<path d="M5,5h90v90h-90"/>
```

また、中央に8x8の正方形を書くと、

```xml
<path d="M46,46H54V54H46"/>
```
```xml
<path d="M46,46h8v8h-8"/>
```

こんな感じで、どちらの方がより短くかけるかはその都度見直してみてください。

## `viewBox` を見直す

扱いが難しい `viewBox` ですが、使いこなすと便利です。

### 小数点を消すために `viewBox` を大きくする

```xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
  <path d="M3.25,5.75 L5.5,6 L0.5,10.25 L10.75,10.75"/>
</svg>
```

`viewBox` と座標をすべて100倍してやると、

```xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 5000 5000">
  <path d="M325,575 L550,600 L50,1025 L1075,1075"/>
</svg>
```

小数点が不要になります。不要になる小数点の数だけ効果があるので、複雑にいくつもの点を定義しているほど効果が大きいです。

ただし、同じサイズで埋め込むために、`width` と `height` の指定が必須になります。

```xml
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 5000 5000">
  <path d="M325,575 L550,600 L50,1025 L1075,1075"/>
</svg>
```

また、大きくなった数は指数表記するというテクニックもあるようです。

```xml
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 5e3 5e3">
```

なお、今回の場合、なにも100倍してやらなくても大丈夫です。小数点以下を見てみると、 `.25`, `.5`, `.75` と0.25きざみなので、4倍してやるだけで大丈夫。

```xml
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 200 200">
  <path d="M13,23 L22,24 L2,41 L43,43"/>
</svg>
```

### 座標の桁数を減らすために `viewBox` で平行移動（並進）する

```xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
  <path d="M10,10 H90 V90 H10"/>
</svg>
```

100 x 100の `viewBox` に、10マージンをあけた正方形を書いています。これを見ると、「ああ、10が9だったら、3バイトも減らせるのに」と思いますよね。

ということで、 `viewBox` を使って10を9にしてしまいましょう。

```xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 100 100">
  <path d="M9,9 H89 V89 H9"/>
</svg>
```

`viewBox` に `-1` を入れているので、2バイト増えてしまいますが、桁数が減る座標が多いほどプラスの効果が出てきます。

もっとワイルドに `viewBox` をいじりましょう。

つまるところ、もっとも座標が密集しているあたりに `0,0` を持ってくると、より短い記述で同等のSVGを作れることになります。

今度は中心に6 x 6の正方形を描きます。

```xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
  <path d="M47,47 H53 V53 H47"/>
</svg>
```

x, y 方向に 47 ずつずらしてやって、こんな感じ。

```xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-47 -47 100 100">
  <path d="M0,0 H6 V6 H0"/>
</svg>
```


# おわりに

例では見やすいように改行、インデント、スペースなどを入れていますが、当然不要なので削除してください。

あと、SVGの知識は最近の付け焼き刃なので、ツッコミください。 

なお、この記事はテレワークデイで削減された通勤時間を使って書かれました :yum: 

# 参考

 - [SVG 1.1 仕様](https://triple-underscore.github.io/SVG11/)
 - [SVG画像を1バイトでも削るためのコードゴルフ](http://d.hatena.ne.jp/rikuo/20131203)
 - [svg要素の基本的な使い方まとめ](http://defghi1977.html.xdomain.jp/tech/svgMemo/svgMemo.htm)
 - [SVGの作図問題とコードゴルフ](http://qiita.com/butchi_y/items/44b35e659bbeeb452d21)

:pray: