SVGスプライトをGatsbyで使う方法

SVGスプライトは複数のデータを1つのファイルにsymbolを使ってまとめたファイルです。 例えばこんなやつ。レスポンシブルデザインでよく見るハンバーガー(三)とxです。

src/assets/svg-sprites.svg
<svg width="0" height="0" class="hidden" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <defs>
        <!-- hamburger(三) -->
        <symbol id="icon-hamburger" viewBox="0 0 24 24">
            <path fill-rule="evenodd" d="M4 5h16a1 1 0 0 1 0 2H4a1 1 0 1 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2z" />
        </symbol>
        <!-- times(x) -->
        <symbol id="icon-times" viewBox="0 0 24 24">
            <path fill-rule="evenodd" d="M18.278 16.864a1 1 0 0 1-1.414 1.414l-4.829-4.828-4.828 4.828a1 1 0 0 1-1.414-1.414l4.828-4.829-4.828-4.828a1 1 0 0 1 1.414-1.414l4.829 4.828 4.828-4.828a1 1 0 1 1 1.414 1.414l-4.828 4.829 4.828 4.828z" />
        </symbol>
    </defs>
</svg>

このように定義したやつをGatsbyで読み込んでuseタグを使ってこんな風に利用する方法を説明します。

<svg>
  <use xmlns="http://www.w3.org/1999/xlink" xlink:href="#svg-sprites_icon-hamburger"></use>
</svg>

SVGスプライトをheadで読み込む方法

Gatsbyでは自分でテンプレートを指定しなければビルド時に自動で生成された .cache/default-html.js (開発時)がテンプレートして使われるようになってますが、このファイルを参考にしてsrc/html.js と上書きすることでテンプレートを変更することができます。

Customizing html.js

これを利用してbodyでSVGスプライトを読み込むようにした例が以下です。

TypeScriptでない例は、.cache/default-html.jsをコピーすればいいだけなので、TypeScriptの例を用意しました。

src/html.tsx
// [ref]
// https://github.com/chitoku-k/chitoku.jp/blob/master/src/html.tsx
import React, { DetailedHTMLProps, ReactNode } from 'react'
import svgSprites from '@/assets/svg-sprites.svg'

const HTML: React.FC<HTMLProps> = ({
  htmlAttributes,
  headComponents,
  bodyAttributes,
  preBodyComponents,
  body: __html,
  postBodyComponents,
}) => {
  return (
    <html {...htmlAttributes}>
      <head>
        <meta charSet="UTF-8" />
        <meta httpEquiv="X-UA-Compatible" content="IE=Edge" />
        <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1.0,user-scalable=yes" />
        {headComponents}
      </head>
      <body {...bodyAttributes}>
        {preBodyComponents}
        <svg style={{ display: 'none', width: '0', height: '0' }} xmlns={"http://www.w3.org/2000/svg"} xmlnsXlink={"http://www.w3.org/1999/xlink"} dangerouslySetInnerHTML={{ __html: svgSprites.content }} />
        <div key="body" id="___gatsby" dangerouslySetInnerHTML={{ __html }} />
        {postBodyComponents}
      </body>
    </html>
  )
}

interface HTMLProps {
  htmlAttributes: DetailedHTMLProps<React.HtmlHTMLAttributes<HTMLHtmlElement>, HTMLHtmlElement>
  headComponents: ReactNode[]
  bodyAttributes: DetailedHTMLProps<React.HTMLAttributes<HTMLBodyElement>, HTMLBodyElement>
  preBodyComponents: ReactNode[]
  body: string
  postBodyComponents: ReactNode[]
}

export default HTML

やっていることは、src/assets/svg-sprites.svg ファイルをモジュールとして読み込んで {preBodyComponent} 直下でdangerouslySetInnerHTMLを使って読み込んでいるだけです。

注意点があって、src/assets/svg-sprites.svg ファイルをモジュールとして読み込むためのローダーが必要です。

Gatsbyなのでプラグインとして用意してあって gatsby-plugin-svg-sprite を利用することでモジュールとして読み込むことができるようになります。コード読めばわかるのですが、Gatsbyの replaceWebpackConfig API を使ってwebpackの設定にローダーを設定しているだけですね。

https://github.com/marcobiedermann/gatsby-plugin-svg-sprite/blob/7ad26bbfa2b8d20c1d59cc0c7c61b6d1e74a16c7/gatsby-node.js#L27-L35

        {
          test: /\.svg$/,
          use: [
            {
              loader: require.resolve('svg-sprite-loader'),
              options,
            },
          ],
        },

このプラグインをgatsby-config.jsに設定してから

import svgSprites from '@/assets/svg-sprites.svg'

として読み込むとsvgSpritesは以下のようなオブジェクトに変換されています。

svgSprites SpriteSymbol {
  id: 'svg-sprites',
  viewBox: '0 0 0 0',
  content: '...長いので省略...'
}

注意点は、モジュールとして解決後、id(SVGスプライトのファイル名)がprefixとしてついてしまうという点です。
なのでicon-hamburgersvg-sprites_icon-hamburgerになってしまってます。

svgSpritesの出力の一部
  content: '<symbol class="hidden" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 0 0" id="svg-sprites">\n' +
    '    <defs>\n' +
    '        <!-- hamburger -->\n' +
    '        <symbol id="svg-sprites_icon-hamburger" viewBox="0 0 24 24">\n' +
    '            <path fill-rule="evenodd" d="M4 5h16a1 1 0 0 1 0 2H4a1 1 0 1 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2z" />\n' +
    '        </symbol>\n' +

必要なのはcontentなのでsvgSprites.contentdangerouslySetInnerHTMLで渡せばいいとなります。

SVGスプライトを利用するためのコンポーネント作成

SVGスプライトを利用するための専用コンポーネントを作っておいたがいいです。 毎回、<svg ... ><use ... /></svg>って書くのは辛いですから。

これは私のブログで使っているコンポーネントです。

src/components/atoms/SvgIcon
import React from 'react'

type Props = {
  className?: string
  style?: any
  xlinkHref: string
}

const SvgIcon: React.FC<Props> = (props) => {
  const { className, style, xlinkHref } = props
  return (
    <svg className={className} style={style}>
      <use xmlns={"http://www.w3.org/1999/xlink"} xlinkHref={`#${xlinkHref}`} />
    </svg >
  )
}

export default SvgIcon

使い方はこうです。

<SvgIcon xlinkHref="svg-sprites_icon-hamburger" />

まとめ

  • SVGスプライトをGatsbyで読み込む方法を説明しました。
  • SVGスプライトをモジュールとして読み込むためにはgatsby-plugin-svg-spriteが必要だと説明しました。
  • SVGスプライトを利用するためのコンポーネントの作例を紹介しました。

以上です。

yukihirop

この記事を書いた人

フロントエンド兼バックエンドなWEBエンジニアです。 既存の考え方に囚われずに自由にツールを作るのが好きです。

いつかBioに「Creator of ...」と書けるようなプログラマを目指している人のブログ

関連記事