Insights

技術情報

Safariで「外部SVG参照のclip-pathが効かない」問題の解決方法(インラインSVGで確実に直す)

問題の概要

画像に対してSVGの clipPath を使い、複雑な形状でマスク(切り抜き)を行う実装において、ChromeやFirefoxでは正常に表示されるものの、Safariでのみクリッピングが適用されず、画像が矩形(四角)のまま表示されるという現象が発生しました。

問題のコード例(外部SVG参照):

.masked-element {
  /* 外部ファイルのIDを参照している場合、Safariで効かないことがある */
  clip-path: url("/assets/images/shapes.svg#my-clip-path");
}

原因:Safariにおける外部参照URLの解決

Safari(WebKit系)では、CSSの clip-path: url("external.svg#id") のように外部SVGファイル内の clipPath を参照する挙動が不安定です。
同一オリジンであっても読み込みに失敗したり、<base> タグの影響でURL解決が意図せず崩れたりと、環境依存の不具合が出やすい傾向にあります。

一方で、同一ドキュメント内(HTML内)に定義された url(#id) への参照は安定して動作します。そのため、外部参照をやめてインライン定義に切り替えるのが、現時点での最も確実な回避策となります。

解決方法:clipPath定義を「インラインSVG」でHTMLに埋め込む

外部SVGファイルの参照をやめ、HTMLページ内に直接 defs > clipPath を配置し、CSSからはIDハッシュ(#id)のみで参照する形に変更します。

1. HTMLにインラインSVG(defs)を追加

対象ページ(または共通のレイアウトファイルなど)の body 直下やコンテンツの先頭付近に、不可視のSVGとして clipPath 定義を配置します。

<div class="layout-wrapper">
  <svg
    aria-hidden="true"
    focusable="false"
    width="0"
    height="0"
    style="position:absolute; left:-9999px; overflow:hidden;"
    xmlns="http://www.w3.org/2000/svg"
  >
    <defs>
      <clipPath id="mask-shape-01" clipPathUnits="objectBoundingBox">
        <path d="M0.595,0.014 C0.634,-0.01 0.687,-0.003 ... (省略) ... Z" />
      </clipPath>

      <clipPath id="mask-shape-02" clipPathUnits="objectBoundingBox">
        <path d="M0.903,0 C0.956,0 1,0.034 1,0.076 ... (省略) ... Z" />
      </clipPath>
    </defs>
  </svg>

  <div class="masked-image">
    <img src="photo.jpg" alt="">
  </div>
</div>

実装のポイント

  • 非表示にする: width="0" height="0" に加え、画面外へ退避(left:-9999px)させ overflow:hidden を指定することで、レイアウトへの影響を防ぎます。
  • アクセシビリティ: aria-hidden="true"focusable="false" を付与し、スクリーンリーダー等のノイズにならないようにします。
  • 名前空間: xmlns 属性を明記し、ブラウザ間の解釈ブレを防ぎます。
  • 座標系: clipPathUnits="objectBoundingBox" を指定する場合、path の座標は 0〜1の相対値 で記述する必要があります(後述)。

2. CSS(SCSS)の参照を url(#id) に変更

CSS側は、ファイルパスを含まないID参照のみに変更します。

.masked-image {
  /* Safari対策: インラインSVGのclipPathを参照 */
  -webkit-clip-path: url(#mask-shape-01);
  clip-path: url(#mask-shape-01);
}
  • -webkit-clip-path は古いバージョンのSafariや一部の環境向けに、念のため併記しておくと互換性が高まります。

実装時の注意点(ハマりどころ)

注意1:IDの競合を避ける

HTML内に直接記述するため、id="mask-shape-01" はページ内で一意である必要があります。
複数ページで読み込まれる共通ヘッダーやフッターに埋め込む場合は、他のIDと被らないようプロジェクト特有の接頭辞を付けるのが安全です。

  • NG: id="mask" (他のライブラリ等と被りやすい)
  • OK: id="site-hero-mask-01"

注意2:元SVGが絶対座標なら「相対座標」へ変換が必要

clipPathUnits="objectBoundingBox" を使用する場合、座標は要素のサイズに対する比率(0〜1)で指定します。元のSVGが width="300" height="400" のような絶対座標で作られている場合、そのままコピペしてもマスク位置が合いません。

変換の考え方

  • X座標の値 × (1 / width) = 相対X座標
  • Y座標の値 × (1 / height) = 相対Y座標

例:幅300pxの場合、各X座標に 1 / 300 (約0.00333) を掛けて変換します。

補足
相対座標化が難しい場合は、clipPathUnits="userSpaceOnUse" を使い、SVG側を viewBox で制御する方法もあります。ただし、CSS側で要素サイズが変わるとマスクが追従しないため、レスポンシブ対応には objectBoundingBox(相対座標)への変換が推奨されます。


この回避策を推奨する理由

  1. クロスブラウザでの安定性
    Safariを含め、主要ブラウザで再現性高く動作します。外部参照に起因する不安定な挙動を根本から回避できます。
  2. 外部ファイル依存の解消
    ネットワークエラーやキャッシュ、パス解決のミスによる表示崩れを防げます。
  3. 保守性
    マスク定義が「使用するコンポーネント」の近く(HTML内)に存在するため、コードの見通しが良くなります。

パフォーマンスに関しては「外部SVGへのリクエストが1つ減る」程度の微細な差ですが、それ以上に「どのブラウザでも確実に表示される」という堅牢性が最大のメリットです。

まとめ

Safariで clip-path: url("external.svg#id") が効かない場合は、外部参照をやめてインラインSVGの defs > clipPath を置き、CSSから url(#id) で参照するのが最も確実な解決策です。

「特定のブラウザだけ表示が崩れる」という問題は、仕様の解釈や実装状況の差で発生しがちです。トリッキーなハックを使うよりも、最も基本的で枯れた技術(インラインSVG)に寄せることが、結果として運用コストを下げることにつながります。