Hugoでアイコン用のSVGスプライトファイルを出力する
HugoでSVGで作られたアイコンを1つにまとめたアイコン用のSVGスプライトファイルを出力する方法を調べました。
SVGスプライトとは
まずはSVGスプライトとは何か?について説明します。
SVGスプライトはサイトで利用するSVGで作られたアイコンなどをSVGノードとして1つのSVGドキュメントにまとめ、利用するときはそこにあるノードを参照して表示させる手法です。
SVGノードの表示に <use> 要素を利用する方法
SVGスプライトでSVGドキュメントにあるノードを表示させる方法の1つにSVGの<use>要素を利用する方法があります。
SVGの<use>要素で表示させるには表示するノードを参照するidをhref属性に指定します。
同じHTMLの中のSVGドキュメントにあるノードだけでなく外部のSVGファイルにあるノードも指定することができます。
<!DOCTYPE html>
<html>
  <head>
    ....
  </head>
  <body>
    ....
    <svg xmlns="http://www.w3.org/2000/svg">
      <!-- 同じHTML内のSVGにあるidがicon-sample-01のノードを指定 -->
      <use href="#icon-sample-01" />
      <!-- 外部のSVGファイルにあるidがicon-sample-02のノードを指定 -->
      <!-- ただしIEは外部のファイルを指定できない -->
      <use href="sample.svg#icon-sample-02" />
    </svg>
    ....
  </body>
</html>SVGの<use>要素で表示させるメリットはCSSでノードのスタイルを変更できる点です。CSSのbackground-imageやHTMLの<img>要素、<object>要素で表示させる場合はノードのスタイルをCSSで変更できません。
<!DOCTYPE html>
<html>
  <head>
    ...
    <style>
      #icon {
        /* SVGのスタイルをCSSで変更できない */
        background-image: url('sample.svg');
      }
    </style>
    ...
  </head>
  <body>
    <!-- SVGのスタイルをCSSで変更できない -->
    <img src="sample.svg" />
    <!-- SVGのスタイルをCSSで変更できない -->
    <object data="sample.svg"></object>
    <!-- SVGのスタイルをCSSで変更できる -->
    <svg>
      <use href="sample.svg#icon-sample-02" />
    </svg>
  </body>
</html>HTMLにインラインで記述したSVGの場合はノードのスタイルをCSSで変更することができます。インラインで記述したSVGの中で<use>要素から参照されるノードも同様です。
ただし参照されるノードやその中のノードのスタイルが属性で設定されている場合はCSSでスタイルを変更することはできません。
<svg xmlns="http://www.w3.org/2000/svg">
  <!-- fillやstrokeなどをCSSで変更できる -->
  <symbol id="sample-icon-01" viewBox="0 0 12 12" width="12" height="12">
    <rect id="rect-1" x="1" y="1" width="10" height="10"  />
  </symbol>
  <!-- fill、strokeをCSSで変更できない -->
  <symbol id="sample-icon-02" viewBox="0 0 12 12" width="12" height="12">
    <circle id="circle-1" cx="6" cy="6" r="5" fill="black" stroke="black" />
  </symbol>
</svg>参照される側のSVGではviewBoxを指定できる<symbol>要素でノードを定義するのが使い勝手がいいです。
アイコン用のSVGスプライトファイルを出力する
それではアイコン用のSVGスプライトファイルを出力する方法について説明していきます。
まずは追加する資材の説明です。アイコン用のSVGファイルを配置するフォルダ、アイコン用のスプライトファイルを作成するHugoのテンプレート、SVGスプライトのアイコンを利用するHugoのテンプレートを追加します。
- /assets/images/icons
- アイコン用のSVGファイルを配置するフォルダです。
 ここに配置したアイコンをファイル名をノードのidにしてSVGスプライト用のファイルにまとめます。
- /assets/images/icon.svg
- アイコン用のスプライトファイルです。
 実際のファイルの内容はHugoのテンプレートで、SVGアイコンを配置するフォルダにあるSVGファイルを読み込んでまとめたSVGファイルを出力します。
- /layouts/partials/icon.html
- アイコン用のSVGスプライトファイルにあるアイコンを利用する側のHugoのpartialテンプレートです。
利用する側はアイコンを表示するpartialにアイコンの参照を指定して表示します。
{{ partial "icon" (dict "name" "アイコンの参照") }}アイコンを利用する側のテンプレート
つぎはアイコンを利用する側のpartialテンプレートについて説明します。
{{- $width  := default 10 .width  -}}
{{- $height := default 10 .height -}}
{{/* "images/icons.svg" をテンプレートとして実行 */}}
{{- $icons := resources.Get "images/icons.svg"
  | resources.ExecuteAsTemplate "images/icons.svg" .
  | resources.Minify
  | fingerprint -}}
{{- $href := printf "%s#%s" ($icons.Permalink | relURL) .name -}}
<i class="icon-svg icon-{{- .name -}}">
  <svg viewBox="0 0 {{ add $width 1 }} {{ add $height 1 }}"
    width="{{ $width }}" height="{{ $height }}">
    <use x="0.5" y="0.5" width="{{ $width }}" height="{{ $height }}"
      xlink:href="{{- $href -}}" href="{{- $href -}}"></use>
  </svg>
</i>このアイコンを表示するpartialテンプレートはdictを引数を取ります。
- name
- アイコンのidを指定します。
 スプライト用のファイルにある指定されたidのノードを表示します。
- width
- アイコンの幅を指定します。指定しない場合は10になります。
- height
- アイコンの高さを指定します。指定しない場合は10になります。
このテンプレートの中で/assets/images/icon.svgを取得してExecuteAsTemplate関数でHugoのテンプレートとして実行してアイコンを表示するスプライトファイルを出力させます。
{{- $icons := resources.Get "images/icons.svg"
  | resources.ExecuteAsTemplate "images/icons.svg" .出力させたスプライトファイルへのURLと引数のnameに指定されたidを<use>タグのhref属性に設定します。
{{- $href := printf "%s#%s" ($icons.Permalink | relURL) .name -}}
...
<svg ...>
  <use xlink:href="{{- $href -}}" href="{{- $href -}}" ...SVGスプライトファイルを出力するテンプレート
つづいてはアイコン用のSVGスプライトファイルを出力するテンプレートです。
<svg xmlns="http://www.w3.org/2000/svg">
{{- range os.ReadDir "/assets/images/icons" -}}
  {{/* <symbol>要素に設定する属性を格納するslice, id属性はファイル名から設定 */}}
  {{- $attrs := slice (printf `id="%s"` (replace .Name ".svg" "")) -}}
  {{- with resources.Get (printf "images/icons/%s" .Name) -}}
    {{/* SVGをXMLとしてパースしてheight, width, viewBoxを属性を取得 */}}
    {{- with $svg := .Content | transform.Unmarshal -}}
      {{- range $key := slice "-height" "-width" "-viewBox" -}}
        {{- with index $svg $key -}}
          {{- $attrs = $attrs | append (printf `%s="%s"` (substr $key 1) .) -}}
        {{- end -}}
      {{- end -}}
    {{- end -}}
    {{/* <svg>要素を<symbol>要素に置換, fill, stroke属性を削除 */}}
    {{- .Content
      | replaceRE `<svg[^>]*>` (printf `<symbol %s>` (delimit $attrs " "))
      | replaceRE `\s*(fill|stroke)=\"[^"]*\"` ``
      | replaceRE `</svg>` `</symbol>`
      | chomp
    -}}
  {{- end -}}
{{- end -}}
</svg>アイコン用のスプライトファイルを出力するテンプレートではos.ReadDir関数で/assets/images/iconsにあるファイルをすべて読み込んで、それぞれのSVGファイルの<svg>要素を<symbol>要素に置換しています。
置換した<symbol>要素には元の<svg>要素に設定されていたwidth属性、height属性、viewBox属性をそのまま設定するようにしています。
CSSで塗りつぶしの色と線の色を変えたいのでSVGのノードに設定されているfill属性、stroke属性を削除するようにしました。
まとめ
HugoではCSSやJavaScript、SVGなどのリソースをテンプレート化することができます。テンプレート化することでビルド時に動的にリソースを出力することができるので便利です。
- The Hugo Gopher is based on an original work by Renée French.