グラフィックをSVGの<symbol>内に一度だけ定義し、各表示用SVG内で<use>を使ってそのIDを参照します。各インスタンスに個別のサイズを指定し、周囲のページの配置はCSSに任せます。SVGドキュメントの構造。

このチュートリアルは、MacでHTMLページを編集していて、同じ小さなグラフィックを複数のテキストラベルの横に表示したい人向けです。完成例では、サンプルタイル、通常のHTML、CSSを使います。フレームワーク、JavaScript、ダウンロードした画像素材、ビルド手順は必要ありません。

1つのシンボルから、複数の表示用グラフィックを作るには?

<symbol>はグラフィックのテンプレートです。それ自体は表示されませんが、<use>で参照すると表示用のインスタンスが作られます。ドキュメント内で定義を一度だけ記述し、グラフィックを置きたい場所に小さなSVGラッパーを追加します。MDNによると、どちらの要素も広く利用できます。MDN: symbol。

重要なのは、作成したパスの重複を避けることと、ブラウザーの描画処理を避けることは別だという点です。HTMLにはパス定義が1つだけ含まれますが、ブラウザーは各<use>に対して表示用インスタンスを作成します。MDNはこれを、外部には公開されないDOMへの複製と説明しています。再利用によってソースを保守しやすくできますが、複数の表示用グラフィックが1つの描画オブジェクトになるわけではありません。MDN: use。

ラベルとアイコンのあるページでは、ラベルをHTMLで記述し、各アイコンに独自のSVGラッパーを用意します。WHATWGは、HTML内のSVGを埋め込みコンテンツ、フレージングコンテンツ、フローコンテンツとして定義しており、通常のページコンテンツの中にこれらのラッパーを配置できます。HTML Standard: SVG。

定義1つとインスタンス3つを使った完全なページを作る

Macのプレーンテキストエディターでsvg-reuse.htmlという名前のファイルを作成します。以下のドキュメント全体を貼り付けて保存し、デスクトップブラウザーで開きます。拡張子.htmlを保ち、リッチテキスト形式ではなくプレーンテキストとして保存してください。

この例では、1つの<path>を使って2つの斜めのタイルを定義しています。3つの参照が、同じ形状を異なるサイズと色で表示します。各参照にはhref="#demo-tiles"を使います。現在の形式は通常のhrefであり、xlink:hrefは非推奨です。MDN: use attributes。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Reusable tile symbol</title>
  <style>
    body {
      margin: 2rem;
      font-family: system-ui, sans-serif;
      color: #263445;
    }
    .symbol-bank {
      position: absolute;
      width: 0;
      height: 0;
      overflow: hidden;
    }
    .samples {
      display: flex;
      flex-wrap: wrap;
      gap: 1.5rem;
    }
    .sample {
      display: flex;
      align-items: center;
      gap: 0.75rem;
      padding: 1rem;
      border: 1px solid #cbd5df;
    }
    .icon {
      display: block;
      width: 24px;
      height: 24px;
      flex: none;
    }
    .icon-large { width: 48px; height: 48px; }
    .icon-medium { width: 32px; height: 32px; }
    .blue { color: #2457a7; }
    .purple { color: #7140a0; }
  </style>
</head>
<body>
  <svg class="symbol-bank" width="0" height="0" aria-hidden="true">
    <symbol id="demo-tiles" viewBox="0 0 24 24"
            preserveAspectRatio="xMidYMid meet">
      <path fill="currentColor"
            d="M4 4H10V10H4Z M14 14H20V20H14Z"></path>
    </symbol>
  </svg>

  <main>
    <h1>Tile samples</h1>
    <div class="samples">
      <div class="sample">
        <svg class="icon" width="24" height="24"
             viewBox="0 0 24 24" aria-hidden="true">
          <use href="#demo-tiles" width="24" height="24"></use>
        </svg>
        <span>Small tiles</span>
      </div>

      <div class="sample blue">
        <svg class="icon icon-large" width="48" height="48"
             viewBox="0 0 24 24" aria-hidden="true">
          <use href="#demo-tiles" width="24" height="24"></use>
        </svg>
        <span>Large tiles</span>
      </div>

      <div class="sample purple">
        <svg class="icon icon-medium" width="32" height="32"
             viewBox="0 0 24 24" aria-hidden="true">
          <use href="#demo-tiles" width="24" height="24"></use>
        </svg>
        <span>Medium tiles</span>
      </div>
    </div>
  </main>
</body>
</html>

期待される結果は、同じ斜めのタイルが入った枠付きサンプルが3つ表示されることです。1つ目は本文の文字色、2つ目は青、3つ目は紫になります。各SVGのボックスは、幅と高さがそれぞれ24、48、32 CSSピクセルです。ブラウザーウィンドウの幅を狭めて、サンプルの折り返しを確認してください。

ここでは隣接するラベルが内容を伝えるため、表示される図形は装飾です。そのため、SVGラッパーにはaria-hidden="true"を使っています。これにより、見た目を隠さずにアクセシビリティツリーから除外されます。この指定は装飾用グラフィックに使い、テキストにない情報を伝える画像には適用しないでください。MDN: aria-hidden。

ページレイアウトとSVGレイアウトを制御する設定は?

役割は3つに分かれています。HTMLコンポーネントの配置、各SVGボックスのサイズ設定、そして描画をそのボックスに合わせることです。ある役割の設定を変更しても、残りの2つには影響しないことがあります。

役割 この例での設定 変更する内容
ページ上でサンプルを配置する .samplesでflexboxと折り返しを使用 ここで間隔や折り返しを変更する
各グラフィックをラベルに揃える .sampleでflexboxとalign-items: centerを使用 ここでコンポーネントの配置を変更する
表示するSVGボックスのサイズを設定する .iconとサイズ用クラス ここでCSSの幅と高さを変更する
描画の座標を定義する viewBox="0 0 24 24" アートワークの座標範囲が変わるときに変更する
参照先のシンボル内部のサイズを設定する <use>の幅と高さは24 この例の内部座標範囲に合わせておく
描画を縦横比を保って収める preserveAspectRatio="xMidYMid meet" 別のフィット方法にする場合のみ変更する

Flexboxは、フレックスコンテナーの直接の子要素を配置します。したがって外側のコンテナーがサンプルを配置し、内側の各コンテナーがSVGとラベルを横に並べます。Flexboxは一度に1つの軸に沿って配置するため、折り返された行はそれぞれ独立してスペースが配分されます。行と列を連動させるデザインには、ページレイアウトの選択肢としてCSS Gridが適しています。MDN: Basic concepts of flexbox。

SVG内では、<use>のレイアウトモデルは異なります。xとyの値は、SVGの座標系内でグラフィックの位置をずらします。HTMLコンポーネント間の間隔は設定しません。幅と高さは、参照先のシンボルのビューポートを設定できますが、パスやグループを直接参照したときの汎用的な拡大縮小設定にはなりません。W3C: Layout of re-used graphics。

大きなインスタンスでも内部寸法が24なのはなぜ?

外側のSVGのviewBoxは、24×24の座標範囲を表します。CSSボックスを48×48ピクセルにしても、内部座標は変わりません。<use>は内部の24×24の範囲いっぱいに描画され、外側のSVGがそれを大きな表示ボックスに合わせます。MDNによると、viewBoxは最小x、最小y、幅、高さの4つの数値で定義されます。MDN: viewBox。

このアートワークを使った計算例では、48ピクセルのボックスに24座標単位を合わせると、倍率は48 / 24 = 2になります。タイルの形状は座標4から20まで広がるため、描画全体の範囲は(20 - 4) × 2 = 32ピクセルになります。残りのスペースは、描画内に意図的に設けた余白です。

このため、アイコンのCSS幅を広げても、描画された形状がその幅全体を必ず埋めるわけではありません。ページの余白を調整して補う前に、viewBox内で図形が占める範囲を確認してください。

SVGボックスが長方形の場合は?

xMidYMid meetでは、描画は縦横比を保って拡大縮小され、中央に配置されて、利用可能なビューポート内にviewBox全体が収まります。そのため、横長の長方形に正方形の描画を置くと、左右に空きができます。noneは変形を許容し、sliceは縦横比を保ちながらviewBoxの一部がビューポートからはみ出すことを許容します。グラフィック全体を表示したいときはmeetを使います。MDN: preserveAspectRatio。

各インスタンスに個別の色を付けるには?

パスにはfill="currentColor"を使います。このキーワードは要素のcolor値を取り込み、同じ描画を周囲のテキスト色に合わせます。この例では、.blueまたは.purpleのルールを変更すると、ラベルとタイルの両方の色が変わります。MDN: currentColor keyword。

これは各インスタンスにスタイルが継承されることで機能します。SVGの再利用モデルでは、生成されるコンテンツは<use>要素をホストとして、そこからスタイルを継承します。ソースパスに直接固定色を指定すると、親から継承される塗りの指定より優先されます。インスタンスごとに色を変える必要がある場合は、共有する形状に固定色を指定しないでください。W3C: Style Scoping and Inheritance。

表示用ラッパーを通じて、生成されたパスを選択する.blue pathのようなセレクターには頼らないでください。生成コンテンツは再利用シャドウツリーに属するため、祖先との関係は通常のHTMLの入れ子とは異なります。ラッパーかそのコンテナーにcolorを設定し、ソース形状にはcurrentColorを残してください。W3C: Style Scoping and Inheritance。

再利用したシンボルの表示がおかしいときに確認すること

参照先から順に確認します。この手順により、グラフィックが表示されない場合と、サイズや位置が正しくない場合を切り分けられるため、それぞれの調整を適切な仕組みに対して行えます。

  1. 参照先を確認します。 href="#demo-tiles"が定義側のid="demo-tiles"と一致していることを確認します。そのIDはドキュメントツリー内で一意にしてください。W3C: The id attribute。
  2. 表示用ラッパーの寸法を確認します。 ゼロサイズの.symbol-bankクラスは定義を格納するコンテナーだけに付けます。.iconのように、表示される各インスタンスにはゼロでないサイズのボックスが必要です。
  3. 座標範囲を確認します。 描画が宣言されたviewBoxの範囲内に収まっていることを確認します。予想外の空白は、使われていない描画座標が原因かもしれません。MDN: viewBox。
  4. 参照先の要素タイプを確認します。 シンボルを直接参照するパスに置き換えた場合、<use>の幅と高さを変更しても同じようには拡大縮小されません。このパターンでは、シンボルとそのviewBoxを使う形に戻してください。MDN: use。
  5. 色の指定を確認します。 インスタンス用のセレクターを増やす前に、ソースに固定色が指定されていないか確認します。
  6. ページのコンテナーを確認します。 グラフィックが正しく表示されているのにラベルから離れすぎている場合は、コンポーネントのCSSの間隔または配置を調整します。MDN: Basic concepts of flexbox。

この例を既存のページに組み込むときは、シンボルバンクを一度だけ挿入し、CSSを統合して、表示用SVGラッパーだけを追加のコンポーネントにコピーします。この小さなドキュメントには、要素の対応関係、IDの一意性、3つの参照が1つのパス定義に対応することについて構造チェックを行いました。ただし、これらのチェックでブラウザー上の表示が確認できたわけではありません。対応させる予定のMac用ブラウザーでページを開き、サイズ、間隔、色を確認してください。

共有する描画を変更するには、その単一のパス定義を編集します。1つのインスタンスだけを変更するには、そのラッパーのサイズまたは継承される色を編集します。役割を分けておけば、アートワークを再利用しながら、各インスタンスの配置を予測どおりに制御できます。

出典