使いまわせるSVG

ロゴとかアイコンとかシンボルとか、Webサイトの中のいろいろなところで使われていて、それでいて色違いも必要だったりする小っこい画像たち。みるみる増えてしまってもう仕方ないですね…X(。そんな小っこい画像をひとまとめに管理できる、SVGスプライトについてまとめます。
アイコン一式SVG
バラバラのアイコンたちをひとまとめにして、取っ散らかった画像フォルダをスッキリ片付けちまいたいXO!
ということで、例えば、ボタンの矢印とかアイコンとか、ロゴとかシンボルとか、Webサイトの至るところに蔓延る小っこいアイコンを、ひとつのSVGファイルに詰め込んで使いたいやつだけ表示させるやつのつくり方をまとめます。
仕組み
「SVGでやることのまとめ。」という記事で紹介したuse要素を使います。
ひとつのSVGファイルに、アイコンぜんぶを詰め込んで、それぞれにid属性を割り振れば、use要素でお目当のアイコンが呼び出せるって寸法ですねー:D!
<svg width="20" height="20" viewBox="0 0 20 20"><use href="ico.svg#id"/></svg>

ここでは、Lopan.jpで使ってるロゴとかアイコンを詰め込んだ「Lopanの詰め合わせSVG」を作りたいと思います:)。
イラレデータの用意
まずはイラレデータを準備。「新規...(⌘+N)」で、ひとまず「Web」プリセットを開きます。
詰め込みたいロゴやアイコンのオブジェクトを、このaiデータに集めます。と言っても、ロゴもアイコンもすでに作り置きのものがあるので、コピペしてくるだけです:D。
素材集め
詰め込みたいアイコンはこちら。Lopan.jpで使っているアイコン(以下、Lopanのアイコン)をひとつ残らずコピペします。
Lopanのアイコンは、塗りは使わず線だけで描くようにしていて、アートボード(後のviewBox)のサイズは20px×20pxに揃えて、線の太さも1pxか2pxに統一しています。
※虫眼鏡アイコンにだけ1.5pxと3pxも使ってました。X9

Lopanのロゴ「パンちゃんアイコン」と、ついでにサイトで使ってるイラストと、Lopan色で作ったブラウザのアイコンも入れて、それからいっそ、SNSとか、いろんなメディアのアイコンも詰め込んじゃいます!

それぞれ公式サイトから、ガイドラインを参照しつつダウンロードしたものをコピペして、でっかいアイコンはひとまず横幅を40pxに縮小して並べてゆきます。

ここからちょっと微調整。
CodePenのアイコンは元データがパスラインで描かれていたので、他のアイコンと同じ塗りになるように、メニューから「オブジェクト > パス」の「パスのアウトライン」を適用し、さらに「パスファインダー」の「合体」を適用しておきます。
Instagramのアイコンにはブランドカラーのグラデーション※を使いたいですね:D。今のままグラデーションを適用すると、外枠と中の図形に別々に適用されてしまうので、こんな時は「複合パス⌘+8」でひとつのオブジェクトにしてしまうと良い感じです。
そんなこんなして、その他のアイコンも見た目が同じくらいの大きさになるように調整して…、
こんな感じに揃えました:)。

詰め込みたいロゴやアイコンぜんぶ出揃ったところで、一旦、「icon.ai」として保存しておきます;)。

公式サイトは以下を参照のこと(2022年11月現在)。
- Twitterについて | Twitterロゴ、ブランドガイドライン、ツイートツール
- Instagramブランド | Instagram | Brand Portal
- ブランド関連情報 - YouTube のしくみ
- LINE APP ICON GUIDELINE
- ブランドの概要 | Facebook app | Brand Portal
- Pinterest ブランド使用ガイドライン | Pinterest Business
- Hatena Brand Resources
- GitHub Logos and Usage · GitHub
- Logos - CodePen Blog
- ロゴデータ – noteヘルプセンター
イラレデータを整える
集めたアイコンたちを、SVGで書き出す用に、レイヤーに名前をつけて、アートボードに配置します。
レイヤーを整える
その前にまず、コピペしっぱなしでハチャメチャしているレイヤーを、整理整頓です:D。
まずはSNSアイコンを、ひとつのレイヤーにまとめちゃいます。
オブジェクトがバラけてるアイコンは、グループ化(⌘+G)してひとつのサブレイヤーにしつつ。

「Layer 1」というレイヤーへ移動することにします。
オブジェクトを移動して空っぽになったレイヤーは削除しちゃえば、だいぶスッキリしますねー:D!

あと、アイコンと一緒に「レイヤー 1」に入ってる、ロゴとイラストとブラウザアイコンも、別々のレイヤーに仕分けます。

そして、レイヤー名もそれぞれ変えておきます。イラストとロゴは「lopan」、SNSアイコンは「sns」、ブラウザアイコンは「browser」、Lopanのアイコンは「icon」というふうにしました:D。
Lopanのアイコンは、ひとつずつ、アイコンを作るとき目安にしたガイドとグループ化しておきます。下図の「<長方形>」というサブレイヤーがガイドのこと。

- ガイドがロックされている場合はガイドのロックを解除(⌘+option+;)しないと触れないので注意。
- ガイドと共に選択したとき、一見選択されてないように見えるけど、⌘を押せば選択されてるのが確認できる。

サブレイヤーにも名前をつける
SVGに書き出した際、レイヤーに付けた名前が、その要素のid属性として反映されるのでした。詳しくは「SVGでやることのまとめ。」という記事を参照のこと。つまり、use要素で呼び出す際に使うid名になるということなので、レイヤーには、そのアイコンだとすぐ分かる名前を付けると、分かり良いですね。
それから、SVGに書き出された際、アイコンはそれぞれ、g要素で括られた状態であって欲しいので、レイヤー名が「<グループ>」じゃないレイヤーは、グループ化(⌘+G)してから、そのグループレイヤーの名前を書き換えるようにします。
ここでは、Safariアイコンには「safari」、Twitterアイコンには「twitter」という要領で、下図のように名付けました。

アートボードに振り分ける
すべてのアイコンに名前を付けたら、SVGで書き出す用のアートボードに割り振ってゆきます。
まずは、Lopanのアイコン用に、20px×20pxのアートボードを用意します。適当な位置に適当な大きさでアートボードを作ってから、returnキーを押して「アートボードオプション」パネルで、アートボードの名前の変更して、サイズを「20px×20px」にします。

Lopanのアイコンをぜんぶ選択したらば、「整列」パネルの整列方法が「アートボードに整列」になっていることを確認し、おもむろに「水平方向中央に整列」、「垂直方向中央に整列」を順にクリック。Lopanのアイコンがすべて「ico」アートボードに重なって配置されました:D!

SNSアイコンとブラウザアイコンも同じ要領で、40px×40pxのアートボードを用意して、名前を書きに変えたら、それぞれアイコンをアートボードに集結させます。
ロゴとイラストはそれぞれ、アートボードツールでクリックして、ぴったしサイズのアートボードを作ってから、名前を書き換え、サイズを整数に調整。
イラストもサイズが同じだったので同じアートボードに重ねちゃいます!

これにて、イラレのデータ準備完了!

SVGに書き出す
イラレデータが整ったら、いよいよ書き出し!
「SVGでやることのまとめ。」の時と同じ「スクリーン用に書き出し...(⌘+option+E)」から、SVGファイルを書き出します。
スクリーン用に書き出し
おもむろにoption+⌘+Eすると、「スクリーン用に書き出し」パネル(下図)が現れます。
すべてをチェックして、すべてのアートボードを選択します。詳しい設定は、前回の記事を参照のこと。

「形式の設定」パネルでは、今回は縮小にチェックすることにします。アイコンいっぱいなので、軽量化を計りましょう:D!

いざ「アートボードを書き出し」をクリックすると、SVGファイルがいっぺんに書き出されましたXD!

SVGコード
書き出されたソースコードはこんな感じ。下ソースコードは「ico.svg」になります。
さすがに圧縮されていますね…!XD
見やすく改行とインデントを入れたのが、下ソースコード。g要素に、アイコンごとのid属性が割り当てれていますが、中には何も属性がない<g>
だけのも紛れてますね…。
<?xml version="1.0" encoding="UTF-8"?>
<svg id="icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
<defs>
<style>
.cls-1,.cls-2,.cls-3,.cls-4{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;}
.cls-2{stroke-width:2px;}
.cls-3{stroke-width:1.5px;}
.cls-4{stroke-width:3px;}
</style>
</defs>
<g id="next">
<polyline class="cls-2" points="7 17 15 10 7 3"/>
</g>
︙
<g id="code">
<g>
<polyline class="cls-2" points="6 4 2 10 6 16"/>
<polyline class="cls-2" points="14 4 18 10 14 16"/>
<line class="cls-2" x1="11" y1="4" x2="9" y2="16"/>
</g>
</g>
<g id="play">
<polygon class="cls-2" points="5 3 5 17 16 10 5 3"/>
</g>
<g id="pause">
<g>
<rect class="cls-2" x="4" y="5" width="4" height="10"/><rect class="cls-2" x="12" y="5" width="4" height="10"/>
</g>
</g>
</svg>
イラレデータに余計なグループレイヤー(下図「<グループ>」)があるので、解除しておきます。

改めて書き出したのがこちら(改行とインデントを入れてあります)。
余計な<g>
が居なくなりましたね:)!
もうひと手間の軽量化
SVGファイルの軽量化を計るならば、イラレデータにもうひと手間加えると、もうちょっと軽くできます。
例えば、さっきグループ化を解除した一時停止アイコンの「<パス>」ふたつを選択して、複合パスにしておくと、ちょっと軽くなります。

書き出したのが下ソースコード。複合パス前はrect要素がふたつあったのに対して、複合パス後はpath要素ひとつだけになって、コード量が半分くらいになってますね!
複合パス前
<g id="pause"><rect class="cls-2" x="4" y="5" width="4" height="10"/><rect class="cls-2" x="12" y="5" width="4" height="10"/></g>
複合パス後
<g id="pause"><path class="cls-2" d="M4,5h4V15H4V5ZM12,15h4V5h-4V15Z"/></g>
けど、複合パスにして大丈夫なのは、同じスタイルのオブジェクトだけ。スタイルが違うオブジェクトを複合パスにしちゃうと、一番下のレイヤーのスタイルに統一されてしまうので注意です。
下図は、外枠が2px、内側のラインが1pxの地球儀アイコンを複合パスにしたところ。内側のラインも2pxになっちゃって、先端の丸型も取れちゃいました:(。
地球儀アイコンは、内側の1pxのラインのみ、複合パスにすることにします。

その他のアイコンも、以下のコラムを踏まえて、複合パスにしたりしなかったりして、さらに改めて書き出し直したSVGソースコードがこちら(改行とインデントを入れてあります)。若干、軽くなりました:D!
SVGをカスタマイズ
書き出されたSVGファイルを使うとき、より使い易いように、もうちょっとだけカスタムです。
さて、use要素を使ってSVGファイル内の要素を読み込むとき、style要素は読み込まれないのでした。なのでまずは、SVGファイル内のstyle要素で定義されているスタイルを、描画可能要素に指定し直してゆきます…:‹!
Lopanのアイコンの場合
「Lopanのアイコン」のstyle要素の内容は、下ソースコードのようになってました。
.cls-1,.cls-2,.cls-3,.cls-4{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;}
.cls-2{stroke-width:2px;}
.cls-3{stroke-width:1.5px;}
.cls-4{stroke-width:3px;}
例えば.cls-2
に指定しているスタイルは、fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;
(以下、最初のそれ)と、stroke-width:2px;
。これを、class="cls-2"
が付いてる要素に、style属性で指定し直せばよいわけですねー。
ただし、線の色だけは、HTML側のCSSで変えられるようにしておきたいので、stroke:#000;
だけは外しておくことにします。それからstroke-widthプロパティは、単位がなくても大丈夫なのでpx
は端折ります。
<g id="next"><polyline class="cls-2" points="7 17 15 10 7 3"/></g>
↓
<g id="next"><polyline style="fill:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:2;" points="7 17 15 10 7 3"/></g>
けどこれを、他のアイコンにも指定していくとなると、結構な量になっちゃいます…⁑(。
なので、最初のそれは、use要素を呼び出すHTML側で指定することにして、SVGファイル内では、stroke-width
だけを指定することにします。stroke-width
だけなら、style属性でstyle="stroke-width:2"
と書くよりもstroke-width属性を使ってstroke-width="2"
と書いた方が短く済みますね:)。
class="cls-1"
だったところはclass属性ごと削除して、class="cls-2"
だったところをstroke-width="2"
、class="cls-3"
→stroke-width="1.5"
、class="cls-4"
→stroke-width="3"
という具合に書き換えます。
<g id="search"><circle class="cls-2" cx="8" cy="8" r="6"/><path class="cls-1" d="M12,8c0-2.209-1.791-4-4-4"/><line class="cls-3" x1="12.25" y1="12.25" x2="14.9" y2="14.9"/><line class="cls-4" x1="14.9" y1="14.9" x2="18" y2="18"/></g>
↓
<g id="search"><circle stroke-width="2" cx="8" cy="8" r="6"/><path d="M12,8c0-2.209-1.791-4-4-4"/><line stroke-width="1.5" x1="12.25" y1="12.25" x2="14.9" y2="14.9"/><line stroke-width="3" x1="14.9" y1="14.9" x2="18" y2="18"/></g>
ぜんぶ書き換えるとこんな感じ。
使う時はこんな風に使います。
<svg width="20" height="20" viewBox="0 0 20 20" class="ico"><use href="ico.svg#search"/></svg>
下のサンプルではstyle要素の中で.ico
に対して、線色だけ変えた最初のそれを指定しています。
ブラウザアイコンの場合
「ブラウザアイコン」のソースコードがこちら(改行とインデントを入れてあります)!
ブラウザアイコンは、色とか変えずにそのまま使うので、style要素で指定されてるスタイルをそのまま「描画可能要素」に書き移すだけでよさそう。
クラス毎に、fillプロパティしか指定がないから今回も、style属性で指定するより、fill属性を使って指定した方が短く済みますね;)。
<g id="safari">
<circle class="cls-7" cx="20" cy="20" r="20"/>
<polygon class="cls-8" points="31.785 8.215 17.643 17.643 22.357 22.357 31.785 8.215"/>
<polygon class="cls-3" points="8.215 31.785 17.643 17.643 22.357 22.357 8.215 31.785"/>
</g>
↓
<g id="safari">
<circle fill="#83bfdf" cx="20" cy="20" r="20"/>
<polygon fill="#dd6486" points="31.785 8.215 17.643 17.643 22.357 22.357 31.785 8.215"/>
<polygon fill="#f4f5f7" points="8.215 31.785 17.643 17.643 22.357 22.357 8.215 31.785"/>
</g>
使う時はこんな感じ。
けれどよ〜く見ると、Firefoxの尻尾の先だけ切れちゃってますね:ᗡ
そういえばイラレでデータ作ってる時も、アートボードから先っぽがハミ出てました…。

そんな時は、svg要素に対してoverflowプロパティでvisible
を指定してあげればよいです。
ハミ出てた部分もちゃんと表示されました;D!
SNSアイコンの場合
「SNSアイコン」のソースコードはこちら(改行とインデントを入れてあります)!
svg要素に、今まで見なかったxmlns:xlink
という属性が書かれてますね…。xmlns:xlink属性は、SVGファイル内でハイパーリンクとか「xlinkの機能」を使っている場合に記述しなくちゃいけない名前空間宣言のための記述なのだけれど、SNSアイコンではxlink
は使っていないので、省略して大丈夫:)。
あとは、ブラウザアイコンと同じく、style要素で指定されてるスタイルをそのまま「描画可能要素」に書き移してゆきます。style要素の内容は、ほとんどfillプロパティだけですけれど、ふたつ、なんかちょっとヘンなのがありますね…。
.cls-9{fill:url(#_名称未設定グラデーション_31);}
fillプロパティの値のurl(#_名称未設定グラデーション_31)
とは、「id属性の値が_名称未設定グラデーション_31
の要素で定義されたグラデーション」を塗り色として指定してるってこと。
SVGでは、図形の塗り色にグラデーションを適用する場合、下ソースコードみたくlinearGradient要素を使って、グラデーションを定義する必要があります。CSSでもlinear-gradient()関数を使ってグラデーションを定義してるので一緒ですね。
けどこっちはXML形式なのでちょっと見慣れないうえ、グラデーションの角度を、x1,y1
とx2,y2
の「2点の座標」でもって調整するところがややこやしポイントです。
<linearGradient id="_名称未設定グラデーション_31" x1="1.988" y1="34.073" x2="40.014" y2="4.363" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#ffd522"/>
<stop offset=".48" stop-color="#f1000b"/>
<stop offset="1" stop-color="#b900b3"/>
</linearGradient>
グラデーションを適用するには、このlinearGradient要素も必要なのですけれど、linearGradient要素は残念ながら、描画可能要素ではないので、この要素もHTML側に移動しなきゃいけません。
あと、id属性が日本語なのはヘンなので、ここではgrad
というid名に変更しておきます。
.cls-10,.cls-11,.cls-2{fill-rule:evenodd;}
それから、fill-ruleプロパティとは、オブジェクトの塗りルールを指定するプロパティ。evenodd
という値にすると、「パスが交差するたびに塗り面を交互に切り替える※」というルールになるんですが、今回つくるアイコンの中には、どこもパスが交差してないんですよねぇ…。なのでこの指定もなくて大丈夫。
イラレのメニュー「効果」の「パスファインダー」で「中マド」を適用した感じ。
そんなこんなで、描画可能要素にスタイルを書き移した「sns.svg」を、use要素を使って表示してみます。
Instagramのアイコンだけ表示されてませんね…、これは例の、塗りがグラデーションのアイコン…。linearGradient要素をHTML側に移動したことで、SVGファイル内で塗り色が適用されずに透明になっちゃってるみたいです…。※Safariではちゃんと表示されてます。(2022年11月現在)
SVGファイル内のfill属性の指定は削除して、HTML側のsvg要素にfill属性を指定することにします。
グラデーションがちゃんと表示されましたね;)!
linearGradient要素、fill-ruleプロパティについて、詳しくは下記ページを参照のこと。
ファビコン用のパンちゃん
ファビコン用なので、単体で使えれば良いのだけど、「Lopanの詰め合わせSVG」に同梱するべく、use要素で使えるようにカスタムしたいと思います!
イラレから書き出した「ファビコン用のパンちゃん」のソースコードはこちら(改行とインデントを入れてあります)!
ほとんどdefs要素の中に書いてあります。defs要素とは、svg要素の中でも、スタイルや効果を定義(definitions)するための要素を記述するための場所で、描画されない要素になります。
ファビコン用のパンちゃんでは、「スタイルの定義」と「グラデーションの定義」と、「クリッピングパスの定義」がされていますね。
<defs>
<style>
︙
</style>
<clipPath id="clippath">
︙
</clipPath>
<linearGradient id="_名称未設定グラデーション_26" x1="7.608" y1="52.392" x2="40.392" y2="-4.392" gradientUnits="userSpaceOnUse">
︙
</linearGradient>
</defs>
<defs>
」そして肝心の、描画可能要素はというと、下ソースコードだけ。長方形がグループ化されてますね…。
<g id="favicon">
<g class="cls-2">
<rect class="cls-3" width="48" height="48"/>
</g>
</g>
つまりこういうこと!グラデーションした長方形をパンちゃん型でくり抜いてる状態です。同じグラデーションでも前述のInstagramアイコンとは違う構造なのですね。

グラデーションもクリッピングパスも定義する系だから、描画可能要素は長方形しかありません。
スタイルを書き移しても、以下の通り、defs要素はuse要素で読み込んでも適用されないから、HTML側に記述するとなると、clip-path属性もfill属性も必要ないし、もうg要素も要らないですね。
<g id="favicon">
<g clip-path="url(#clippath)">
<rect fill="url(#_名称未設定グラデーション_26)" width="48" height="48"/>
</g>
</g>
となると、favicon.svgの中身は下ソースコード(長方形)だけ…?
<?xml version="1.0" encoding="UTF-8"?>
<svg id="favicon" xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
<rect width="48" height="48"/>
</svg>
use要素で表示させてみたものの、ほとんどHTMLに記述してるので、わざわざ外部ファイルにする必要はなさそう…。※日本語グラデーション名はgrad
に変えてます。
グラデーションやクリッピングパスをふんだんに使用したSVGファイルは、use要素での表示には向いてない、ということです:/。
のこりのパンちゃん
「パンちゃんアイコン」と「イラスト」は、「Lopanのアイコン」と同じく、全体に基本スタイルを指定して、パーツ毎に線の太さだけ指定しています。
HTML側で線色自在です;D。(※下サンプルのSVG1が「pan.svg」で、SVG2が「panchan.svg」)
アイコンをひとまとめにする
さぁ、これまでに用意したアイコンたち(ファビコン以外)を、ひとつのSVGファイルに詰め込みますよ!
g要素を「ico.svg」へじゃんじゃかコピペして、出来上がったのがこちら!
SVG画像としてimg要素で配置すると、20px×20pxの矩形の中に、アイコンぜんぶが重なった状態で表示されます。つまり「ico.svg」というひとつのSVGファイルの中に、文字通りすべてのアイコンが詰まってる状態なんですねーXD!
「ico.svg」自体のsvg要素のサイズは「20px×20px」だけれど、svg要素を使ってHTMLに表示させる時(使う時)のwidth/height/viewBox属性の値は、元のsvg要素のサイズを指定します。
viewBox属性(左上と右下のXY座標)でもって表示する範囲を指定して、width/height属性で表示サイズを指定してるってこと;)
まるで四次元ポケットから取り出して使ってるみたいですね:D。
Lopanのアイコン
<svg width="20" height="20" viewBox="0 0 20 20"><use href="ico.svg#download"></use></svg>
SNSアイコン
<svg width="40" height="40" viewBox="0 0 40 40"><use href="ico.svg#instagram"></use></svg>
パンちゃんイラスト
<svg width="82" height="115" viewBox="0 0 82 115"><use href="ico.svg#sorry"></use></svg>

width/height/viewBox属性のサイズを間違えると下のサンプルのようになってしまうので注意。
width/height
を間違うと表示サイズが小さくなって、viewBox
を間違うと表示する範囲が狭くなっちゃってます(下サンプル)。
ico.svgの中のアイコンをぜんぶ並べてみました。
あとがき
…上のサンプルをよく見てみたら、右向きの矢印「」を入れ忘れてることに気づきました…:|。
そんな時も焦らず、イラレで新たに、20px×20pxの右向き矢印を作って、アートボードとレイヤー名を整えたらば、すみやかにスクリーン用に書き出し...!

<?xml version="1.0" encoding="UTF-8"?><svg id="_レイヤー_1" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2px;}</style></defs><g id="arrow_next"><path class="cls-1" d="M10,3l8,7-8,7m8-7H2"/></g></svg>
書き出されたSVGファイルのg要素の部分だけ、ico.svgにコピペして、class="cls-1"
をstroke-width="2"
に変えれば、追加完了!(arrow_prev
とarrow_up
の間に入れました。)
あとからでも簡単に編集できちゃうところがSVGの良いところ:D。
︙
<g id="arrow_prev"><path stroke-width="2" d="M10,17L2,10,10,3M2,10H18"/></g>
<g id="arrow_next"><path stroke-width="2" d="M10,3l8,7-8,7m8-7H2"/></g>
<g id="arrow_up"><path stroke-width="2" d="M3,10L10,2l7,8M10,2V18"/></g>
︙
てなもんで以上、「使いまわせるSVG(の作り方)についてのまとめ」でした;)。
最後まで読んでいただきありがとうございました!

SVGのフィルター効果