Skip to content

Instantly share code, notes, and snippets.

@podhmo
Last active July 28, 2025 12:04
Show Gist options
  • Save podhmo/7950c771b37e915c48465e8535ae63c1 to your computer and use it in GitHub Desktop.
Save podhmo/7950c771b37e915c48465e8535ae63c1 to your computer and use it in GitHub Desktop.
svgの不満点を挙げさせてみた

対話履歴

ユーザー: htmlと比較したsvgの不満点を挙げてください。可能な限り。正確に言うとブラウザがサポートするべきであるはずなのにリソースが投下されずにそのままになってる仕様のことかもしれません。

つまりsvgで完結することができず仕方がなくhtmlのdomとjsで頑張ることになる意思決定のことです(あるいはcanvas)。

例えばの例としてズーム時のテキストのサイズとかが挙げられると思います。


AI: Web開発において、HTMLと比較した場合のSVGの不満点、特にブラウザのサポートが不十分で、結果的にHTMLのDOMとJavaScript、あるいはCanvasで代替せられるを得ない仕様について、具体的な例を挙げて解説します。ご指摘の「ズーム時のテキストのサイズ」の問題も含め、開発者が直面しやすい不満点は以下の通りです。

1. テキスト処理能力の限界

SVG単体では、HTMLが持つ高度なテキストレイアウト機能が大きく不足しています。

  • テキストの自動折り返しが標準サポートされていない: SVG 1.1仕様では、テキストの自動折り返し機能が定義されていません。 SVG 2ではCSSベースのテキストレイアウトに準拠し、自動折り返しが導入される予定ですが、ブラウザの対応はまだ十分とは言えません。 そのため、現状では<tspan>要素を使い、JavaScriptで単語や文字ごとに位置を計算して改行をシミュレートするという、非常に手間のかかる実装が必要になります。 あるいは、HTML要素を埋め込める<foreignObject>を使って、HTMLのテキストレイアウト機能に頼ることになります。

  • 垂直方向のセンタリングが困難: HTMLのCSSではdisplay: flexalign-items: centerで簡単に実現できるテキストの垂直中央揃えが、SVGではdominant-baseline属性などを使いますが、直感的ではなく調整が難しい場合があります。

2. レイアウトと重ね合わせ順序の制御

HTMLのCSSが提供する強力なレイアウトシステムは、SVGには存在しません。

  • ボックスモデル、Flexbox、Gridの欠如: SVGには、要素の配置や間隔を柔軟に制御するためのCSSボックスモデルや、Flexbox、Gridといったレイアウト機能がありません。要素の配置はx, y座標やtransform属性で個別に行う必要があり、レスポンシブなレイアウトや複雑なUIの構築はHTMLに比べて格段に複雑になります。

  • z-indexによる重ね合わせ制御ができない: HTMLではz-indexプロパティで要素の重なり順を簡単に制御できますが、SVGでは「ペインターズモデル」が採用されており、DOMツリー上で後に記述された要素が前面に描画されます。 動的に要素の重なり順を変更するには、JavaScriptでDOM要素を直接並べ替える必要があり、非常に面倒です。

3. スケーリング(拡大・縮小)時の挙動

ご指摘の通り、ズーム時の要素の制御はSVGの大きな課題の一つです。

  • テキストや線の太さが一律にスケーリングされる: SVGはスケーラブル(Scalable)なのが利点ですが、ビューポート全体を拡大・縮小すると、中のテキストや図形の線の太さ(stroke-width)も一緒に拡大・縮小されてしまいます。 地図のようなUIで、ズームレベルに関わらずラベルの文字サイズや道路の線の太さを一定に保ちたい、といった要求を実現するには工夫が必要です。
    • 解決策の難しさ: vector-effect="non-scaling-stroke" というプロパティを使えば、線の太さだけはズームの影響を受けないようにできます。 しかし、テキストには同様の機能が標準で用意されていません。そのため、JavaScriptでズームレベルを監視し、その逆のスケールをテキスト要素に適用して、見た目のサイズを一定に保つといった煩雑な処理が必要になります。

4. パフォーマンスの問題

特に要素数が多くなると、SVGはパフォーマンス上のボトルネックになることがあります。

  • 大量のDOM要素によるパフォーマンス低下: SVGはDOMベースであるため、数千、数万といった大量のオブジェクトを描画しようとすると、DOMツリーが肥大化し、ブラウザのレンダリングパフォーマンスが著しく低下することがあります。 このようなケースでは、ピクセル単位で直接描画を制御できるCanvasの方が高速な場合があります。 <defs><use>を使って描画を最適化する方法もありますが、根本的な解決には至らないこともあります。

5. 外部コンテンツとの連携

SVG内で外部の、あるいはHTMLのコンテンツを扱うための<foreignObject>は強力な機能ですが、いくつかの制約があります。

  • <foreignObject>の制約: SVG内にHTMLを埋め込むことができる<foreignObject>は、一見するとSVGの欠点を補う万能な解決策に見えます。 しかし、埋め込まれたHTMLコンテンツに対する外部からのスクリプティングやスタイリングには制限があり、またCORS(Cross-Origin Resource Sharing)ポリシーの影響を受けるなど、予期せぬ挙動を示すことがあります。 さらに、<foreignObject>自体のサポートもブラウザによって微妙に異なる場合があり、必ずしも安定した機能とは言えません。

これらの不満点は、SVGが元々静的なベクターグラフィックを表示するためのフォーマットとして設計されたことに起因する面が大きいです。Webアプリケーションの複雑なUIコンポーネントをSVGのみで完結させようとすると、上記のような壁に突き当たることが多く、結果としてより柔軟で高機能なHTML/CSS/JavaScript、あるいは大量描画に特化したCanvasといった技術を選択せざるを得ないのが現状です。

(返答終了理由: STOP)


ユーザー: 直近の対応で便利になりそうな機能などは存在しますか?


AI: はい、SVGの不便な点を解消し、将来的に便利になりそうな機能や仕様の動きはいくつか存在します。ただし、SVG 2.0の仕様策定が停滞気味であるため、多くの機能はSVG単体の仕様としてではなく、CSSなど関連するWeb標準の進化に取り込まれる形で進んでいます。

直近の対応で特に期待される、あるいは便利になってきている機能は以下の通りです。

1. CSSレイアウト機能のSVGへの適用

これまでSVGの大きな弱点だったレイアウトに関して、CSSの強力なレイアウト機能であるFlexboxやGridをSVG要素に直接適用しようという動きが進んでいます。

  • 現状:
    • Firefoxは、SVG要素に対して display: flexdisplay: grid を適用する対応で先行しています。
    • ChromeやSafariなど他のブラウザでのサポートはまだ限定的ですが、これが標準的に利用可能になれば、SVG要素をHTMLのブロック要素のように整列させることができ、座標を一つ一つ手計算する必要がなくなります。 これは、SVGでUIコンポーネントを構築する際の生産性を劇的に向上させる可能性を秘めています。

2. Houdini APIによる描画能力の拡張

Houdiniは、開発者がブラウザのCSSエンジン(レンダリングプロセス)に直接アクセスできるようにする一連の低レベルAPI群です。これにより、これまでブラウザ内部でしかできなかった描画ロジックをJavaScriptで定義できるようになります。

  • 期待される効果:
    • CSS Painting API: このAPIを使うと、JavaScriptで独自の描画処理を定義し、それをSVGのfill(塗り)やstroke(線)として利用できます。 例えば、「特定のパターンで点滅する塗り」や「データに応じて動的に変化するグラデーション」などを、パフォーマンスを維持しつつ実現できる可能性があります。
    • Custom Layout API: HoudiniのレイアウトAPIが成熟すれば、SVG内で独自のレイアウトアルゴリズムを定義することも理論上は可能になり、テキストの回り込みなどもより柔軟に実装できる未来が考えられます。

3. CSS Motion Pathの標準化

これは既に多くのモダンブラウザで利用可能になっている機能ですが、SVGとの連携において非常に強力です。

  • 機能: CSSのアニメーション機能を使って、HTMLやSVGの要素を、SVGで定義された任意のパス(曲線や図形)に沿って動かすことができます。
  • 利便性: これまではJavaScriptで複雑な計算を行わなければ実現できなかった「キャラクターを特定の軌道で歩かせる」「オブジェクトを円形に周回させる」といったアニメーションが、CSSだけで宣言的に記述できるようになりました。

4. テキスト関連の改善(CSS経由での進化)

SVGのテキスト折り返し問題に対する直接的な解決策はまだ道半ばですが、CSS側のテキスト処理機能の進化が、間接的にSVGにも恩恵をもたらす可能性があります。

  • CSS Text Module: CSSではtext-wrap: balance(バランスの取れた行分割)など、より高度なテキストレイアウト機能の導入が進んでいます。 これらのプロパティが将来的にSVGの<text>要素にも適用されるようになれば、現状の不便さは大きく改善されると期待されます。

まとめ

大きな流れとして、SVGが抱える問題をSVG単体の仕様改訂で解決するのではなく、CSSという強力なスタイリング・レイアウト言語の機能をSVGにも取り込むことで解決しようという方向性が明確になっています。

特に 「CSSレイアウト(Flexbox/Grid)のSVGへの適用」 は、実現すれば日々の開発におけるSVGの使い勝手を最も大きく変えるインパクトを持っています。まだブラウザ間の互換性に課題はありますが、今後の動向を注視する価値が最も高い分野と言えるでしょう。

(返答終了理由: STOP)


ユーザー: それぞれの不満点について実例を挙げて説明する文脈的独立性の高い文章を作成してください。必要なら章を分けてください。長くなっても構いません途中でやめずに全て書ききってください。

全てに対して理解しやすくするためのコードスニペットを積極的に挿入してください。

フォーマットはmarkdownを意図してます。太字や斜体は一切使わないでください。余分な自己陶酔的表現も一切使わないでください(洗練されたなどの表現すら不要です)。


AI: 以下に、HTMLと比較した際のSVGの不満点について、それぞれを文脈的に独立した文章として、コードスニペットを交えて説明します。

テキストの自動折り返し機能の欠如

HTMLでは、CSSプロパティを指定するだけで、要素の幅に合わせてテキストを自動的に折り返すことができます。これはウェブページの基本的な機能です。

<!-- HTMLでのテキスト折り返しの例 -->
<div style="width: 200px; border: 1px solid black; padding: 5px;">
  このテキストはコンテナの幅に応じて自動的に折り返されます。特別な処理は不要です。
</div>

一方、SVGの標準仕様には、このようなテキストの自動折り返し機能が存在しません。<text>要素内のテキストは、改行を指定しない限りどこまでも横に伸び続けます。

<!-- SVGではテキストがはみ出す例 -->
<svg width="250" height="100" xmlns="http://www.w3.org/2000/svg">
  <rect x="10" y="10" width="200" height="80" fill="none" stroke="black" />
  <text x="15" y="30">
    このテキストはコンテナの幅を無視してはみ出してしまいます。
  </text>
</svg>

この問題を回避するには、主に二つの方法が取られますが、どちらもHTMLに比べて複雑です。

一つは、JavaScriptを使用してテキストを単語や文字ごとに分割し、幅を計算しながら<tspan>要素を使って手動で改行をシミュレートする方法です。これは動的なテキストの場合、処理が非常に煩雑になります。

もう一つは、<foreignObject>要素を使い、SVGの内部にHTMLの描画領域を作成して、その中でHTMLのテキストレイアウト機能を利用する方法です。これはSVG内で完結できず、HTMLの機能に依存することになります。

<!-- foreignObjectを使った回避策の例 -->
<svg width="250" height="100" xmlns="http://www.w3.org/2000/svg">
  <rect x="10" y="10" width="200" height="80" fill="none" stroke="black" />
  
  <foreignObject x="10" y="10" width="200" height="80">
    <!-- foreignObject内ではHTMLが使えます -->
    <div xmlns="http://www.w3.org/1999/xhtml" style="padding: 5px;">
      このテキストはforeignObjectとHTMLの力で折り返されます。
    </div>
  </foreignObject>
</svg>

要素の重ね合わせ順序の制御

HTMLでは、CSSのz-indexプロパティを使うことで、要素の重ね合わせの順序を数値で明確に制御できます。数値が大きいほど手前に表示されます。

<!-- HTMLでのz-indexの例 -->
<style>
  .box { position: absolute; width: 100px; height: 100px; }
  .red { background: red; top: 10px; left: 10px; z-index: 1; }
  .blue { background: blue; top: 30px; left: 30px; z-index: 2; } /* z-indexが大きいので手前 */
</style>

<div class="box red"></div>
<div class="box blue"></div>

SVGにはz-indexに相当するプロパティがありません。要素の重ね合わせ順序は、XML文書内での記述順によって決定されます。これを「ペインターズモデル」と呼び、後から記述された要素ほど手前に描画されます。

<!-- SVGでの重ね合わせ順序の例 -->
<svg width="150" height="150" xmlns="http://www.w3.org/2000/svg">
  <!-- 先に書かれた円が奥になる -->
  <circle cx="50" cy="50" r="40" fill="red" />
  <!-- 後から書かれた円が手前になる -->
  <circle cx="80" cy="80" r="40" fill="blue" />
</svg>

この仕様のため、例えばユーザーのアクションに応じて特定の図形を最前面に移動させたい場合、CSSのプロパティを変更するようにはいきません。JavaScriptを使って、対象のSVG要素をDOMツリーの末尾に移動させる、といった直接的なDOM操作が必要になります。

// JavaScriptでSVG要素を最前面に移動させる例
const svg = document.getElementById('my-svg');
const redCircle = document.getElementById('red-circle');
const blueCircle = document.getElementById('blue-circle');

// 赤い円をクリックしたら、それを最前面(DOMの末尾)に移動させる
redCircle.addEventListener('click', () => {
  svg.appendChild(redCircle);
});

// 青い円も同様
blueCircle.addEventListener('click', () => {
  svg.appendChild(blueCircle);
});
``````xml
<!-- 上記JavaScriptに対応するSVG -->
<svg id="my-svg" width="150" height="150" xmlns="http://www.w3.org/2000/svg">
  <circle id="red-circle" cx="50" cy="50" r="40" fill="red" style="cursor: pointer;" />
  <circle id="blue-circle" cx="80" cy="80" r="40" fill="blue" style="cursor: pointer;" />
</svg>

レイアウトシステムの不在

HTMLとCSSは、FlexboxやGridといった強力なレイアウトシステムを提供します。これにより、要素を整列させたり、均等に配置したり、レスポンシブなレイアウトを組んだりすることが容易に行えます。

<!-- Flexboxによる均等配置の例 -->
<style>
  .container { display: flex; justify-content: space-around; width: 300px; border: 1px solid black; }
  .item { width: 50px; height: 50px; background: skyblue; }
</style>

<div class="container">
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
</div>

SVGには、このようなレイアウトシステムが存在しません。要素の配置は、基本的にx, y, cx, cyといった座標属性や、transform属性による移動、回転、拡大・縮小で指定します。複数の要素を均等に配置する場合、それぞれの座標を手動で計算する必要があります。

<!-- SVGで手動で均等配置する例 -->
<svg width="300" height="70" xmlns="http://www.w3.org/2000/svg" style="border: 1px solid black;">
  <!--
    コンテナ幅300、アイテム幅50。
    アイテム3つで合計150。残りスペースは150。
    4つの隙間に均等に分けると、150 / 4 = 37.5。
    1つ目のx = 37.5
    2つ目のx = 37.5 + 50 + 37.5 = 125
    3つ目のx = 125 + 50 + 37.5 = 212.5
    このような計算が必要になる。
  -->
  <rect x="37.5" y="10" width="50" height="50" fill="skyblue" />
  <rect x="125" y="10" width="50" height="50" fill="skyblue" />
  <rect x="212.5" y="10" width="50" height="50" fill="skyblue" />
</svg>

この計算は、要素数が増えたり、動的に要素が変化したりするUIをSVGで構築する際に、開発の複雑さを増大させる大きな要因となります。


ズーム時のスケーリング制御の問題

SVGはScalable Vector Graphicsの名の通り、拡大・縮小しても品質が劣化しないのが特徴です。しかし、このスケーリングはビューポート内の全ての要素に一様に適用されます。その結果、図形の線の太さ(stroke-width)やテキストのサイズまで一緒に拡大・縮小されてしまいます。

これは、地図のようなアプリケーションにおいて問題となります。ズームインしても道路の線の太さや地名のテキストサイズは一定に保ち、ユーザーが読みやすい状態を維持したいという要求が一般的だからです。

線の太さについては、vector-effect="non-scaling-stroke"というプロパティを使用することで、ズームレベルに関わらず太さを一定に保つことが可能です。

<!-- non-scaling-strokeの例 -->
<svg width="200" height="100" viewBox="0 0 200 100" xmlns="http://www.w3.org/2000/svg">
  <g transform="scale(0.5)">
    <!-- この円の線は細くなる(stroke-widthが0.5倍される) -->
    <circle cx="100" cy="100" r="80" stroke="red" stroke-width="10" fill="none" />
    
    <!-- この円の線は太さを維持する -->
    <circle cx="300" cy="100" r="80" stroke="blue" stroke-width="10" fill="none" 
            vector-effect="non-scaling-stroke" />
  </g>
</svg>

しかし、このvector-effectはテキスト要素には適用されません。そのため、テキストサイズをズームレベルに関わらず一定に保つには、JavaScriptで現在のズーム(transformscale値)を取得し、その逆数をテキスト要素のtransformに適用し続けるといった、煩雑なスクリプト処理が必要になります。

// テキストサイズを固定するための概念的なJavaScript
function updateTextScale() {
  const svg = document.querySelector('svg');
  const g = document.querySelector('#zoomable-group');
  const text = document.querySelector('#fixed-size-text');

  // g要素に適用されているスケール値を取得 (実際にはもっと複雑)
  const currentScale = g.transform.baseVal.getItem(0).matrix.a;
  
  // テキストに逆スケールを適用してサイズを一定に見せる
  const inverseScale = 1 / currentScale;
  text.setAttribute('transform', `scale(${inverseScale})`);
}

// ズームが変更されるたびにこの関数を呼び出す必要がある
// (例: マウスホイールイベントなど)

この実装はパフォーマンスに影響を与える可能性があり、SVG単体で完結できないという不満につながります。

(返答終了理由: STOP)

メタデータ

{
  "runSettings": {
    "temperature": 1,
    "model": "models/gemini-2.5-pro",
    "topP": 0.95,
    "topK": 64,
    "maxOutputTokens": 65536,
    "safetySettings": [
      {
        "category": "HARM_CATEGORY_HARASSMENT",
        "threshold": "OFF"
      },
      {
        "category": "HARM_CATEGORY_HATE_SPEECH",
        "threshold": "OFF"
      },
      {
        "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
        "threshold": "OFF"
      },
      {
        "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
        "threshold": "OFF"
      }
    ],
    "enableCodeExecution": false,
    "enableSearchAsATool": true,
    "enableBrowseAsATool": false,
    "enableAutoFunctionResponse": false,
    "thinkingBudget": -1,
    "googleSearch": {}
  },
  "systemInstruction": {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment