SVG use インスタンスの周囲に予期しない余白がある場合は、その原因がソースの viewBox、アスペクト比に基づくフィッティング、周囲の CSS 行ボックスのどれなのかを特定します。このガイドでは、配置座標を予測しやすく保ちながら、それぞれの原因を診断して修正する方法を説明します。

この作業は、小規模なデスクトップ向けウェブページの編集です。再利用可能な図形を SVG 内の正確な位置に配置し、その周囲の HTML レイアウトが意図したとおりに収まるようにします。Mac のプレーンテキストエディタで例に沿って作業し、保存した HTML ファイルを最新のデスクトップブラウザで開けます。

余白を制御している座標系はどれですか?

確認すべき境界は3つあります。外側の SVG 要素の CSS ボックス、参照されたシンボルに使われるビューポート、そしてそのシンボル内の実際の図形です。W3C は、SVG ビューポートとブラウザの CSS ビューポートを明確に区別しています。viewBox は SVG ユーザー座標を SVG ビューポートに対応付けるものであり、ブラウザウィンドウを表すものではありません。Coordinate Systems, Transformations and Units — SVG 2

たとえば、use 要素上の x="20" は、その SVG 座標系におけるオフセットを指定します。ブラウザウィンドウの左端から CSS ピクセルで20の位置という意味ではありません。拡大縮小や祖先要素の変換によって、そのオフセットがページ上でどこに表示されるかは変わります。SVG 仕様では、use による配置を追加の平行移動として説明しており、CSS ボックスレイアウトとは動作が異なります。Document Structure — SVG 2

まず、次の判断表を確認してください。これは、互いに置き換え可能な修正方法を並べたものではなく、文書化されたレイアウトの仕組みに基づく診断手順です。

観察される状態 最初に確認するもの 考えられる修正
SVG 全体の下に細いすき間がある インライン整形と親要素の行の高さ 単独の SVG をブロックレベルにするか、インライン配置を調整する
繰り返し表示される各図形の内側に余白がある 図形の座標とシンボルの viewBox インセットが意図したものでなければ、ソースの境界を詰める
シンボルインスタンスの左右に余分な余白がある インスタンスの寸法と viewBox の比率 比率を一致させるか、意図したフィッティング方式を選ぶ
ローカルな配置は正しいが、ページ上の位置が違う SVG ユーザー単位とブラウザビューポート座標 x と y を設定する前に座標を変換する
インスタンスの寸法を変えても何も変わらない href の対象となる要素 フィッティングにはビューポートを定義するシンボルを使うか、図形を変換する

ローカルの完全な例で余白を再現する

次の完全なドキュメントをプレーンテキストエディタで svg-spacing.html として保存し、ブラウザで開いてください。ライブラリや外部ファイルは使わず、サンプルのラベルと長方形だけを使っています。薄い色の長方形はインスタンスのビューポートを、濃い色の長方形は再利用される図形を示します。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>SVG spacing example</title>
  <style>
    body {
      margin: 24px;
      font: 16px system-ui, sans-serif;
    }
    #canvas {
      display: block;
      width: 320px;
      height: 200px;
      outline: 1px solid #687580;
      background: #fff;
    }
    .viewport-guide { fill: #e4edf2; }
    .artwork { fill: #356b8c; }
    text { fill: #253744; font-size: 14px; }
  </style>
</head>
<body>
  <p>Two instances with different source bounds and fitting dimensions.</p>
  <svg id="canvas" xmlns="http://www.w3.org/2000/svg"
       width="320" height="200" viewBox="0 0 320 200"
       role="img" aria-labelledby="drawing-title">
    <title id="drawing-title">Padded and tightly fitted rectangles</title>
    <defs>
      <symbol id="padded" viewBox="0 0 100 100">
        <rect id="source-shape" class="artwork"
              x="20" y="20" width="60" height="60" />
      </symbol>
      <symbol id="tight" viewBox="20 20 60 60">
        <rect class="artwork"
              x="20" y="20" width="60" height="60" />
      </symbol>
    </defs>

    <rect class="viewport-guide"
          x="20" y="20" width="120" height="60" />
    <use href="#padded" x="20" y="20" width="120" height="60" />
    <text x="160" y="55">Padded source</text>

    <rect class="viewport-guide"
          x="20" y="110" width="60" height="60" />
    <use id="tight-instance" href="#tight"
         x="20" y="110" width="60" height="60" />
    <text x="110" y="145">Tight source</text>
  </svg>
</body>
</html>

両方のインスタンスで寸法を明示的に設定しています。これにより、余白の原因を調べる際に暗黙のサイズ指定に頼らずに済みます。MDN は use について、参照先の SVG コンテンツを再利用する要素と説明しており、参照先が viewBox を持つ SVG またはシンボルの場合には、その width と height がフィッティングに影響するとしています。この例では現在の href 属性を使っています。MDN: use

最初の長方形が実際にどこから始まるかを計算する

最初のソースの viewBox は100×100ですが、長方形が占める座標は各方向とも20から80までです。指定されたインスタンスのビューポートは120×60です。既定の xMidYMid meet フィッティングでは、縮尺は 120 / 100 と 60 / 100 の小さい方、つまり 0.6 です。縮小後の viewBox は60×60となり、水平方向の両側にそれぞれ30単位の余白ができます。これは W3C のビューポート変換アルゴリズムに基づいています。SVG 2: Computing the equivalent transform of an SVG viewport

この説明用の入力では、描画される長方形は水平方向に 20 + 30 + 20 × 0.6 = 62、垂直方向に 20 + 20 × 0.6 = 32 から始まります。各方向のサイズは 60 × 0.6 = 36 です。この計算は、小規模で決定的な計算によって確認しました。これらは計算値であり、ブラウザテストで測定した値ではありません。

2つ目のインスタンスでは、独立した2つの入力を変更しています。ソースの境界を 20 20 60 60 にし、インスタンスのビューポートを60×60にしています。その結果、長方形はビューポート全体を埋め、SVG 座標の (20, 110) から始まります。自分で原因を切り分ける場合は、入力を一度に1つだけ変更してください。

インスタンス内の余白をなくすには?

ソースの境界に意図しない余白があれば修正する

viewBox は、最小 x、最小 y、幅、高さの4つの数値で構成されます。原点がゼロである必要はありません。この例の viewBox="20 20 60 60" は、図形の形状を書き換えずに既存の境界を表しています。MDN: viewBox

実際の図形の境界を詰める前に、そのインセットが意図したものかどうかを確認してください。再利用する図形のセットでは、見た目のサイズと位置を一貫させるために、共通の座標枠を使うことがあります。各シンボルの境界を個別に詰めると、その関係が変わる可能性があります。一貫性がデザインの一部であれば、共通の境界を維持してください。

図形の境界を調べるには、getBBox() を使うと SVG ユーザー座標での境界がわかります。既定ではストロークとマーカーは含まれず、対象要素またはその親要素に適用された変換も考慮されません。そのため、塗りつぶしだけを基準にしたバウンディングボックスでは、ストロークや変換のある図形を切り抜く根拠として不十分です。MDN: SVGGraphicsElement getBBox()

アスペクト比が一致しない場合のフィッティング方法を選ぶ

ソースの境界を詰めても、正方形のソースを横長のビューポートに収めると、周囲に余白ができます。求める結果に応じて方法を選んでください。参照先シンボルの内部フィッティングを調整するには preserveAspectRatio をそのシンボルに指定し、外側の座標対応を診断するには外側の SVG に指定します。MDN: preserveAspectRatio

選択肢 結果と条件
ソースとビューポートの比率を一致させる 縦横比を維持したまま、アスペクト比フィッティングによる余白をなくす
xMidYMid meet viewBox 全体を中央に表示する。比率が異なると余白ができる
xMinYMin meet フィッティング後の viewBox を左上寄りに配置する。余白は残る
none 縦横で異なる縮尺を許容する。正方形が長方形になる場合がある
xMidYMid slice ビューポートを覆うが、ソースの一部はその外にはみ出す。表示範囲はクリッピングで決まる

配置を変えるとフィッティングによる余白の位置が変わりますが、ソースの図形にすでに含まれているインセットがなくなるわけではありません。同様に、ソースの全コンテンツをビューポート内に収める必要がある場合、slice は適しません。これらの違いは、配置、meet、slice、および縦横比を維持しないフィッティングに関する MDN の定義に基づいています。MDN: preserveAspectRatio

寸法を変えても効かない場合は参照先を確認する

use 要素が単純なグループやパスを参照しても、それだけでフィッティング用ビューポートが作られるわけではありません。W3C によれば、参照先の要素が新しいビューポートを定義しない場合、インスタンスの width と height は効果がありません。インスタンスごとのフィッティングを予測しやすくするには、上の例のように、明示的な境界を持つシンボルに再利用する図形を配置してください。図形を直接拡大縮小する場合は、変換を使い、その配置への影響も考慮してください。SVG 2: Layout of re-used graphics

外側の SVG の下にできるすき間をなくすには?

SVG の外側にできるすき間は、HTML のインライン整形が原因の場合があります。WHATWG は SVG をフレージングコンテンツとして扱えると定めているため、ページ内で SVG をテキストと並べて配置できます。CSS の行ボックスにはベースラインの上と下のフォントメトリクスが含まれるので、インライン画像の下に見えるすき間ができることがあります。HTML Standard: SVG、CSS 2: Line height calculations

完全な例ではすでに #canvas に display: block を指定しているため、その SVG はインラインの行ボックスに参加しません。単独の図形にはこのルールを使ってください。仕組みを調べるには、一時的に display: inline に変更し、周囲の行を確認してください。

テキストの横に SVG を置く必要がある場合は、外側の SVG に vertical-align: bottom を指定し、テキストの配置を確認してください。このプロパティはインラインレベルのボックスを行内で配置するものであり、シンボルの境界や内部のフィッティングは変えません。ほかのコンテンツや親要素の行の高さによって、行全体の高さが決まる場合もあります。MDN: vertical-align

次の CSS 宣言は外側の SVG に対する選択肢として扱い、各インスタンスにまとめて貼り付ける修正方法とは考えないでください。

/* Standalone drawing */
#canvas {
  display: block;
}

/* Alternative when the drawing must stay inline */
/*
#canvas {
  display: inline;
  vertical-align: bottom;
}
*/

座標がブラウザビューポート由来の場合は?

getBoundingClientRect() は要素の位置をブラウザビューポート基準で CSS ピクセル単位で返します。その位置はページのスクロールに応じて変わります。この値は SVG ユーザー座標とは異なる空間のものなので、left と top をインスタンスの x と y にそのままコピーするのは、一般に正しくありません。MDN: Element getBoundingClientRect()

入力がブラウザビューポート上の点である場合は、座標変換を使ってください。getScreenCTM() は SVG の変換行列を返し、その逆行列を適用すると、その点を選択した要素のローカル座標に戻せます。点の matrixTransform() メソッドで変換できます。MDN: getScreenCTM()、MDN: matrixTransform()

次のコンソール用スニペットは、上の完全なドキュメントに追加できる任意の拡張です。ブラウザビューポート上の点 (100, 150) を外側の SVG のユーザー座標に変換し、境界を詰めたインスタンスをその位置に移動します。変換されていない例のインスタンスが、その SVG の直接の子要素であることを前提としています。

(() => {
  const canvas = document.getElementById("canvas");
  const instance = document.getElementById("tight-instance");
  if (!canvas || !instance) {
    throw new Error("Open the complete SVG spacing example first.");
  }

  const matrix = canvas.getScreenCTM();
  if (!matrix) {
    throw new Error("The SVG coordinate matrix is unavailable.");
  }

  const viewportPoint = new DOMPoint(100, 150);
  const localPoint = viewportPoint.matrixTransform(matrix.inverse());
  if (!Number.isFinite(localPoint.x) || !Number.isFinite(localPoint.y)) {
    throw new Error("The SVG coordinate matrix cannot be inverted.");
  }

  instance.setAttribute("x", String(localPoint.x));
  instance.setAttribute("y", String(localPoint.y));
})();

有限数かどうかの確認が重要なのは、逆行列を作れない行列では NaN の成分が生じるためです。レイアウトやスクロールが変わった後に新しい点を変換するときは、行列を再計算してください。インスタンスが変換済みのグループ内にある場合は、その親グループの座標系に変換してください。このスニペットは調査時に API ドキュメントと照合しましたが、ブラウザでは実行していません。MDN: DOMMatrixReadOnly inverse()

最後に、3つの境界を個別に確認してください。外側の SVG ボックス、フィッティング後のシンボルビューポート、そして図形自体の境界です。不要な余白を生じさせている境界を修正し、意図したページサイズで結果を確認します。原因のわからないすき間を任意のオフセットで埋め合わせずに、予測しやすい配置を実現できます。

出典