アットコスメ共通のアイコン配布において最適なSVGを研究しました

デザイン部所属のフロントエンドエンジニア竹内美帆です。

アットコスメでは、グラフィカルなものよりUIを構成するのにアイコンを利用することが多いです。アイコンは、Retinaディスプレイでもガビガビにならずに表示できるSVGでの実装が主流です。

現在、開発効率アップとUI統一の基盤ということで、いろんなサービスで利用されるであろう共通アイコンを社内向けにパッケージ化して各サービスに配布する予定です。
各サービスは共通アイコンパッケージをインストールし、単体で利用、スプライトで利用、vue.js内でrequireするなど想定しています。

もちろん今までも気を付けてSVGは実装していましたが、1つ1つの要素や属性に対して何故こうなっているのか説明できるようにしないと、配布側としての責任があるなと考えました。
どのようなSVGファイルが、各サービスで利用されるのに最適かをSVGを復習しつつまとめてみました。

基本の要素

SVGはXMLでもあるので、XML宣言、文書型宣言、ルート要素(svg要素)の3つで構成されています。

■XML宣言
<?xml version="1.1" standalone="no" encoding="UTF-8"?>

■文書型宣言
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">

■ルート要素
<svg></svg>

XML宣言と文書型宣言は今まで省略して書いていました。特に表示も問題なかったですが、なぜ省略できるのかというところを調べてみました。

XML宣言は、SVGの文字コードがUTF-8かUTF-16で、XMLのバージョンが1.0、スタンドアロン文書宣言がNOという条件(例:HTML文書内などで利用される)を満たしていれば省略できます。

文書型宣言に関しては、W3Cの1.3 SVG の名前空間, 公開識別子, システム識別子 W3C SVG 1.1 仕様の日本語訳によると、推奨されていないのでこちらも記述しなくて大丈夫なようです。

ということで、基本構成としてはルート要素であるsvg要素から開始で決まりました。

svg要素に付与する属性

名前空間

ブラウザなどのユーザーエージェントが、このsvgファイルの処理する方法を選択するために名前空間を指定します。
試しにこれを削除するとブラウザで表示されませんでした。

<svg xmlns="http://www.w3.org/2000/svg">

HTMLにインラインでuseで埋め込むこともあるため、下記の名前空間の指定もする必要があります。

<svg xmlns:xlink="http://www.w3.org/1999/xlink">

■useで外部参照した例
<svg viewbox="0 0 34 29"><use xlink:href="/svg/spn/top/icon-top.svg#ranking"></use></svg>

viewBox属性

アットコスメでは、アイコンの大きさを画面によって変える場合があるので、widthとheight属性は指定せず、CSSで指定します。
CSSで指定したwidthとheightにフィットさせるために、viewBox属性の指定が必要になります。これがないとsvg要素のwidth、heightを指定してもはみ出したりしてしまいます。
viewBox属性はaiファイルから書きだしたときに設定されていた値を指定しておきます。

<svg viewbox="0 0 34 29">

SVGファイルがHTMLから内部参照される場合、style要素中のid・class名は他スタイルにも影響を与える

aiファイルから、コードを書きだしたときに、下記のようなstyle要素がついていることがあります。
.st0、.st1などのクラス名は、svg要素内だけのpathやrect要素に効くかと思いきや実はある条件下においては、他HTML上の同じクラス名にも影響を与えます。

~省略~
<style type="text/css">
.st0{fill:none;stroke:#333333;stroke-width:1.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
.st1{fill:none;}
</style>
<g>
<path class="st0" d="M19.4,16.5c1.3-1.3,2.1-3,2.1-4.9c0-4.3-4.3-7.8-9.5-7.8s-9.5,3.5-9.5,7.8c0,4.3,4.3,7.8,9.5,7.8c1.8,0,3.4-0.4,4.9-1.1l3.8,1.8L19.4,16.5z"/>
<rect class="st1" width="24" height="24"/>
</g>
~省略~

なので、下記のように、style要素は削除し、直接各要素に対してstyle属性で付与する形に修正します。

~省略~
<g>
<path style="fill:none;stroke:#333333;stroke-width:1.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;" d="M19.4,16.5c1.3-1.3,2.1-3,2.1-4.9c0-4.3-4.3-7.8-9.5-7.8s-9.5,3.5-9.5,7.8c0,4.3,4.3,7.8,9.5,7.8c1.8,0,3.4-0.4,4.9-1.1l3.8,1.8L19.4,16.5z"/>
<rect style="fill:none;" width="24" height="24"/>
</g>
~省略~

スタイルが適用される理由

useやimg要素などでSVGファイルが外部参照されるされる場合は、shadow-rootになりその空間がclosedになるので影響はありません。
下記は外部参照した場合の開発者ツールでのDOMです。

svgファイルが外部参照の場合にshadow-rootになっている

現状、IE11がまだuseでの外部参照に対応していないため、事前にHTML内にsvg要素をajaxで読みこまして対応しています。
そうするとHTML内にsvg要素がそのまま展開されるので、shadow-rootにはならず、svg要素内に記述されているstyle要素がその画面のスタイルに影響します。
さらに、vueファイルでの利用時にはrequireして、html-loaderでHTML内に入れているため、この方法でも同じようにグローバルになってしまいます。

下記はSVGファイルをajaxでHTML内に入れたものです。先ほどのように、shadow-rootになっていません。また、下記のようなstyle要素を設定しました。

<style>
  .content-menu-item {
    border:1px solid #f00;
  }
</style>

svgがhtml内で内部参照のときはshadow-rootになっていない

スマホ版のアットコスメトップのナビゲーションのクラス名に対して、#f00のボーダーを設定しまして、見事に適用されました。

svg要素の中に設定したcssが適用された

fill、strokeの指定をCSSから指定できるようにしておく

CSSでwidth、heightの指定もなのですが、さらにfill、strokeの微調整ができるようにとデザイナーさんから要望がありました。

fillとstrokeの例

下記のようにサービス側からスタイルを当てる前提なので、配布するSVGには直接style属性には指定しません。

■fillアイコンの場合
svg {
  fill: #333;
}

■strokeアイコンの場合
svg {
  fill: none;
  stroke: #333;
  stroke-width: 1.3px;
  stroke-linecap: round;
  stroke-linejoin: round;
}

必要な場合はtitle要素を入れる

imgでは画像が表示されない場合のalt(代替テキスト)を入れるのと同じように、SVGのアクセシビリティを考慮します。
それと同じように、装飾的なアイコンにはtitle要素は入れず、アイコンにテキストが含まれていたり、テキストが横に付かない場合はアイコンだけの表示になってしまうのでtitle要素を付与します。

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
<title>ログイン</title>
</svg>

最後に不要な記述を削除

改行やスペース、タブなどを削除しできるだけ容量を軽くした状態に整形します。
これはCSSやJSを圧縮するのと同じですね。

最適化されたSVGファイル作りには、デザイナーさんの協力も必要

HTMLやCSSと違い、SVGファイルはaiファイルから書き出すことになるため、aiファイル内のデータの作りもSVGに影響します。

不要な記述削除の手間削減や軽量なSVGファイルを作成するには、aiファイルの時点でデータをシンプルかつ簡潔に作成することが必要です。

よって、デザイナーさん側ではアイコン作成時に下記のようなチェック項目が設けられています。

  • 同じパスが2重に存在していないか
  • 余分なパスが含まれていないか
  • 非表示レイヤーが含まれていないか
  • グループが不必要に深くなっていないか

最適化前と後の例

1つずつコードを見てきましたが、最後にどうなったか例をお見せします。
下記がaiファイルを開き、書きだしたいアイコンをコピー&ペーストした状態です。
かなり不要な要素や属性、改行、インデントなどが含まれていますね。

<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In  -->
<svg version="1.1"
     xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
     x="0px" y="0px" width="24px" height="24px" viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
<style type="text/css">
    .st0{fill:none;stroke:#333333;stroke-width:1.3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
    .st1{fill:none;}
</style>
<defs>
</defs>
<g>
    <path class="st0" d="M19.4,16.5c1.3-1.3,2.1-3,2.1-4.9c0-4.3-4.3-7.8-9.5-7.8s-9.5,3.5-9.5,7.8c0,4.3,4.3,7.8,9.5,7.8
        c1.8,0,3.4-0.4,4.9-1.1l3.8,1.8L19.4,16.5z"/>
    <rect class="st1" width="24" height="24"/>
</g>
</svg>

説明してきた通りに最適化していきます。容量も756から、342byteに減りました。

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><g><path style="stroke-miterlimit:10;" d="M19.4,16.5c1.3-1.3,2.1-3,2.1-4.9c0-4.3-4.3-7.8-9.5-7.8s-9.5,3.5-9.5,7.8c0,4.3,4.3,7.8,9.5,7.8c1.8,0,3.4-0.4,4.9-1.1l3.8,1.8L19.4,16.5z"/><rect style="fill:none;" width="24" height="24"/></g></svg>

最後に

大量のSVGファイル扱うときは、1つずつ手間かけられない場合もあります。
そんなときは下記のような最適化ツールがあるので、お好みの設定をして時間短縮してみてください。

ブラウザから SVGO で SVG ファイルを最適化できる 「SVGOMG」

結構細かい設定が可能ですが、残念ながら今まで説明したものに関して設定で調整するのは出来なさそうでした。

今回はアットコスメに寄せる形で記述方法を書いてきましたが、
SVG配布する予定あるとか、SVGの細かい部分あんまり見てこなかったという方の参考になれば嬉しいです。

参考文献

デザイン部所属フロントエンドエンジニア。コーディング歴10年超え。ガリガリ実装より、環境の標準化ガイドライン作り、新卒・中途コーディング研修などに注力。HTML好きで結構前ですが、HTML5/CSS3コンテスト入賞経験もあり。でもたまにはJavaScriptやPHPでの実装もやりたくなる。2019年3月現在はwebpack-dev-serverでのサーバー起動などを勉強中。