困る前に知っておきたい SVGの基本再解釈

## はじめに
アイコンやロゴなどで使用されることが多いSVGですが、よくわからずにとりあえずで使ってしまうと意外なところでハマってしまったりします。
SVGをソースレベルで解釈しつつ、現状で自分なりにベストな使用方法を探っていきます。

## 対象

### SVGってあれでしょ？よく知らないけど
- 拡大しても画像が綺麗なやつ
- ちょっと使ってみたけど挙動がよくわからないやつ
- ぱっと見でコードがごちゃごちゃしてて何やってるかよくわからないので、結局JPGとかPNGにしちゃうやつ

このくらいの人を対象に想定して書いています。
**「アニメーションとかパーツごとにごりごり動かしたりはしないけど、CSSでアイコンの色を変えたりレスポンシブに対応させたりはしたいなー」**くらいな感じで使用していくときの前提知識として読んでいただけると幸いです。

**「SVGチョットデキル」**という人はまとめまで飛んでいただくと良いかと思います。

## そもそもSVGを使うメリットって？

以下のようなボタンコンポーネントを例に確認していきます。
![Normal Raised Light@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390806/08c7d5de-aad8-b6e7-3438-61a114312f96.png)


### 拡大しても画像が綺麗
PNGやJPGは**ラスターグラフィックス**であり、SVGは**ベクターグラフィックス**であるというところがキーワードになります。
ピクセルごとに色を割り振るラスターグラフィックスに対し、ベクターグラフィックスは「どの座標からどこまで線を引く」というパスから描画を行うため、拡大してもピクセル単位の荒れが出ません。

- PNG（ラスターグラフィックス）
![ラスターグラフィックス参考画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390806/58f01aab-e06e-bb56-301f-9774927f669c.png)

- SVG（ベクターグラフィックス）
![ベクターグラフィックス参考画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390806/3823b236-89d7-bcdd-b39c-a9b236bbf83f.png)

### データ容量が比較的小さい
SVGはパスと座標のデータが大部分のテキストデータですので、データ容量が比較的小さく済みます。またテキストエディターで開き、編集することも可能です。
アイコンやロゴなどは線や色の規則性がコードで表現しやすいため、SVGでの使用に向いていますが、色や線が複雑な写真などを表示するのには不向きであると言えます。

### 色などをCSSで制御できる
パスの中身を塗りつぶす色などを、WebページのCSSから制御することができます。
「SVGで置いたアイコンの背景色などによって画像の色を少し変えたい！」というときなど、わざわざ複数の画像を用意する必要がない訳ですね。

### もう少し掘り下げておくと
規格としてはxml（拡張可能なマークアップ言語）をベースとしたテキストデータです。
整形したSVGデータを眺めてみるとHTMLの記述にも近いものがあり、大体の構造が見えてくるかと思います。
次節で基本的な記述方法を把握し、SVGデータを眺めていきます。

## 基本的なSVGデータの記述

### &lt;svg&gt; 要素
SVGデータを記述するための外枠です。
ビューポートの大きさであるwidth, heightや、アスペクト比となるviewBoxも記述されます。

width, height, viewBox指定の有無で、SVGデータは以下のように描画されます。

- width, heightが指定されている場合
縦横のサイズが固定されます。
- viewboxのみが指定されている場合
縦横の比率は保たれますが、サイズが固定されていないのでレスポンシブに対応させることが可能です。
- 全て指定されていない場合
基本的にはSVGのサイズのデフォルト値としてwidth="300px", height="150px"で表示されるようです。

詳細は以下をご覧ください。
[HTMLタグリファレンス svg要素](https://developer.mozilla.org/ja/docs/Web/SVG/Element/svg)

### &lt;defs&gt; 要素
defs要素内に定義された要素はそのままでは描画されません。
SVGデータ内で同じようなパスを使いまわしたい場合など、defs要素内で定義しておいてdefs要素外から参照する、というような使い方をします。
参照される要素は、可能なかぎりdefs要素内で定義されることが推奨されています。

詳細は以下をご覧ください。
[HTMLタグリファレンス defs要素](https://developer.mozilla.org/ja/docs/Web/SVG/Element/defs)

### &lt;path&gt; 要素
画像を描画するためのパスを記述します。
パスを記述する要素として、&lt;rect&gt; や &lt;circle&gt; など沢山もありますが、&lt;path&gt; 要素はこれらの元となる要素であり、他の描画要素は可読性の向上や記述量の削減のためにショートカットとして定義されているもののようです。
描画要素についてはキリがなさそうなので省略しますが、よく見かけるものとして以下のような要素があります。

```
<circle>
<ellipse>
<line>
<polygon>
<polyline>
<rect>
```

詳細は以下をご覧ください。
[HTMLタグリファレンス path要素](https://developer.mozilla.org/ja/docs/Web/SVG/Element/path)

### &lt;g&gt; 要素
パスのグルーピングを行う要素です。HTMLでのdivタグのようなものだと思っておけば良さそうです。

詳細は以下をご覧ください。
[HTMLタグリファレンス g要素](https://developer.mozilla.org/ja/docs/Web/SVG/Element/g)

このくらいを把握しておけば、ソースを眺めたときにSVGの実態をざっくり掴むことができるかと思います。

## SVGのソースを眺めてみる

Adobe XDでボタンコンポーネントをSVGとして書き出し、ソースを眺めてみます。
XDではSVGの書き出し時に`内部CSS`と`プレゼンテーション属性`で選択が可能です。
それぞれ以下のように書き出しが行われます。

### 内部CSSでの書き出し （1325バイト）
```xml
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="36" viewBox="0 0 90 36">
  <defs>
    <style>
      .cls-1 {
        fill: none;
      }

      .cls-2 {
        clip-path: url(#clip-path);
      }

      .cls-3 {
        fill: #009688;
      }

      .cls-4 {
        fill: #fff;
        font-size: 14px;
        font-family: Roboto-Medium, Roboto;
        font-weight: 500;
      }

      .cls-5 {
        filter: url(#長方形_182);
      }
    </style>
    <clipPath id="clip-path">
      <rect class="cls-1" width="90" height="36"/>
    </clipPath>
    <filter id="長方形_182" x="-3" y="-1" width="96" height="42" filterUnits="userSpaceOnUse">
      <feOffset dy="2" input="SourceAlpha"/>
      <feGaussianBlur stdDeviation="1" result="blur"/>
      <feFlood flood-opacity="0.239"/>
      <feComposite operator="in" in2="blur"/>
      <feComposite in="SourceGraphic"/>
    </filter>
  </defs>
  <g id="Normal_Raised_Light" data-name="Normal Raised Light" class="cls-2">
    <g class="cls-5" transform="matrix(1, 0, 0, 1, 0, 0)">
      <rect id="長方形_182-2" data-name="長方形 182" class="cls-3" width="90" height="36" rx="2"/>
    </g>
    <text id="Normal" class="cls-4" transform="translate(16 23)"><tspan x="0" y="0">NORMAL</tspan></text>
  </g>
</svg>
```

`内部CSS`での書き出しでは、&lt;defs&gt; で囲まれた &lt;style&gt; 要素内にCSSが記述され、グルーピングされたパスにスタイルが当てられていることが確認できます。


### プレゼンテーション属性での書き出し （1045バイト）

```xml
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="36" viewBox="0 0 90 36">
  <defs>
    <clipPath id="clip-path">
      <rect width="90" height="36" fill="none"/>
    </clipPath>
    <filter id="長方形_182" x="-3" y="-1" width="96" height="42" filterUnits="userSpaceOnUse">
      <feOffset dy="2" input="SourceAlpha"/>
      <feGaussianBlur stdDeviation="1" result="blur"/>
      <feFlood flood-opacity="0.239"/>
      <feComposite operator="in" in2="blur"/>
      <feComposite in="SourceGraphic"/>
    </filter>
  </defs>
  <g id="Normal_Raised_Light" data-name="Normal Raised Light" clip-path="url(#clip-path)">
    <g transform="matrix(1, 0, 0, 1, 0, 0)" filter="url(#長方形_182)">
      <rect id="長方形_182-2" data-name="長方形 182" width="90" height="36" rx="2" fill="#009688"/>
    </g>
    <text id="Normal" transform="translate(16 23)" fill="#fff" font-size="14" font-family="Roboto-Medium, Roboto" font-weight="500"><tspan x="0" y="0">NORMAL</tspan></text>
  </g>
</svg>

<text id="Normal" transform="translate(16 23)" fill="#fff" font-size="14" font-family="Roboto-Medium, Roboto" font-weight="500"><tspan x="0" y="0">NORMAL</tspan></text>`
```

`プレゼンテーション属性`では内部CSSは記述されず、各パスに属性としてスタイルが割り振られています。
可読性は内部CSSでの書き出しの方が高そうですが、今回のボタンコンポーネントに関してはプレゼンテーション属性の方が容量が小さいようですね。

## Webでの扱い

WebページにSVGを表示させたいとき、HTMLにSVGデータを直書きする方法（インラインSVG）と、外部画像ファイルとしてimgタグでの読み込む方法があります。
こちらも簡単に比較してみます。

### インラインSVG の場合

-  メリット
  - タグやクラスに個別にスタイルを当てることができる
  - SVGの機能を全て使用することができる

- デメリット
  - HTMLに直書きすることになるので、コードが煩雑になり可読性が低下する
  - HTMLのファイルサイズが肥大化してしまう

### 外部画像ファイルとしてimgタグでの読み込み の場合

- メリット
  - PNG画像やJPG画像と同じように扱うことができる（imgタグで拡張子を変えるだけ）ため、HTMLへの記述がコンパクトにまとまる

- デメリット
  - パーツごとにスタイルを当てることはできない

## SVGの取り扱い方法 個人的メモ

現状、ロゴなどでSVGを使用する場合においては以下の取り扱いをすると良いかと考えています。

### 使用時は外部画像ファイルとしてimgタグで読み込み
パーツごとのアニメーションなどが必要なければ、HTMLの見通しやSVGファイルごとの差し替えを考慮し、外部ファイルとして読み込んでおくのが良いかと思います。

### 書き出しはプレゼンテーション属性で行う
プレゼンテーション属性で記述されたスタイルは優先順位が低く、CSSなどでの上書きが容易なようです。
画像があまり複雑でないアイコンやロゴなどであれば、内部CSSでスタイルを切り離すよりもこちらの方が扱いやすいかと思います。

またSVGデータの扱いにおいて、複数のSVGデータを一つにまとめるSVGスプライトという方法が取られることがありますが、プレゼンテーション属性での記述であれば内部CSSの衝突を避けることができそうです。
(こちらは未確認です。)

### svg要素に記述されているwidthとheightは削除

svg要素に記述されているwidthとheightは、画像のサイズを固定してしまいます。
レスポンシブに対応させるため、こちらの記述は削除しておきます。
画像のアスペクト比はviewBoxで指定しておき、WebページのCSS側で大きさを指定します。

**注意点**
width, heightを指定せずにbackgroundでSVGを読み込んだ場合、IEで表示崩れが起きることがあるようで、この場合に関してはwidth, heightは削除しない方が良さそうです。
imgタグで読み込む分には問題ないかと思っているのですが、他にも表示崩れがある場合、ご教授いただけると幸いです。

## まとめ
現状このあたりを最低限意識しておくと、比較的ストレスなくSVGが使用できることかと思います。

- 外部画像ファイルとしてimgタグで読み込み
- 書き出しはプレゼンテーション属性で行う
- svg要素に記述されているwidthとheightは削除

クリスマスまであと三週間ですね、皆様良い12月をお過ごしください！