地図ピンのような不規則な形のSVGに、背景ぼかしをかけるCSS実装
下記画像のピンのように不規則な形のSVGの背景にCSSでぼかしをかけようとしたところ、思いの外詰まりました。うまくいくまでの過程と解決方法を解説します。

FigmaのBackground Blur効果をSVGに適用した状態で書き出した場合は、インラインSVGとして描写することで比較的簡単に実現できます。今回はSVG側にぼかしが設定されていない場合の対処法となります。
使用したコード
今回使用するHTMLとCSSは以下の通りです。
<button class="pin"></button>.pin { width: 75px; aspect-ratio: 46 / 59; background-image: url(../img/common/icon_pin.svg); background-repeat: no-repeat; background-size: contain;}HTMLにはSVGを配置せず、widthとaspect-ratioでサイズを指定しつつbackground-imageプロパティでSVG画像を描写しています。この時の見た目は次のようになります。

背景のストライプがくっきり見え、この時点でぼかし効果はありません。
まず試したこと
ぼかしを表現するために、CSSにbackdrop-filter: blur(4px);を追加しました。backdrop-filterは、要素の背面に対してぼかしをかけることができるプロパティです。
.pin { width: 75px; aspect-ratio: 46 / 59; background-image: url(../img/common/icon_pin.svg); background-repeat: no-repeat; background-size: contain; backdrop-filter: blur(4px); // ぼかしを表現するために追加}追加すると次のような見た目になりました。

ぼかしはかかっていますが、画像要素のコンテナに対して適用されていることが確認できます。
backdrop-filterはSVGのpathの形ではなく、要素の矩形領域に対してかかるため、形に沿ったぼかしは実現できません。MDNのbackdrop-filterでも適用対象に以下のように記載されていました。
適用対象:すべての要素。 SVG の場合は
<defs>要素やすべてのグラフィック要素を除いたコンテナー要素に適用される
以上のことから別のアプローチが必要となります。
clip-pathによるアプローチ
pathの形に沿ってクリッピングすることで解決できるのではと考え、次のようにCSSを追加しました。
.pin { width: 75px; aspect-ratio: 46 / 59; background-image: url(../img/common/icon_pin.svg); background-repeat: no-repeat; background-size: contain; backdrop-filter: blur(4px); // clip-pathでSVGのパス形状をそのまま参照 clip-path: path( "M45 22.9455C45 35.0656 23 57.8 23 57.8C23 57.8 1 35.0656 1 22.9455C1 10.8253 10.8497 1 23 1C35.1503 1 45 10.8253 45 22.9455Z" );}pathの形状はSVGのd属性(ピンのパスの形状)をコードから直接取得してきています。
<svg viewBox="0 0 46 59" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M45 22.9455C45 35.0656 23 57.8 23 57.8C23 57.8 1 35.0656 1 22.9455C1 10.8253 10.8497 1 23 1C35.1503 1 45 10.8253 45 22.9455Z" fill="#F0F4F7" fill-opacity="0.8" stroke="white" stroke-width="2" stroke-linejoin="round"/> <path d="M24.7172 29.5665C24.0332 29.7487 23.3283 29.8412 22.6204 29.8415C20.7647 29.8413 18.9665 29.1978 17.5321 28.0205C16.0978 26.8432 15.116 25.2049 14.7541 23.3849C14.3922 21.5649 14.6726 19.6757 15.5474 18.0392C16.4223 16.4027 17.8375 15.1202 19.552 14.4102C21.2664 13.7001 23.174 13.6065 24.9497 14.1453C26.7254 14.6841 28.2594 15.8219 29.2903 17.3649C30.3211 18.9079 30.7851 20.7605 30.6031 22.6072C30.4211 24.454 29.6045 26.1804 28.2922 27.4925" fill="#F0F4F7" fill-opacity="0.8" stroke="#FF535E" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/> <path d="M32.9325 32.1333L28.2919 27.4927" stroke="#FF535E" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>すると次の画像のようになりました。

クリッピングされていることを確認できますが、当初のピンの形状とは異なるサイズで切り取られていることがわかります。pathの指定がうまくいっていないようです。
調べたところ、SVGのpath() の座標はピクセル固定のため、要素がリサイズされても座標は追従しないということがわかりました。確かにSVGの幅46pxに対して、CSSで幅75pxを指定していました。これがクリップのサイズがずれる原因のようです。
最終的な解決策
path()で直接座標を指定するのではなく、インラインSVGのclipPathをCSSから参照する方法に転換しました。次のようにCSSで参照するインラインSVGをHTMLに追加しました。
<button class="pin"></button>
<svg width="0" height="0"> <defs> <clipPath id="pin-clip" clipPathUnits="objectBoundingBox"> <path d="M0.978 0.388C0.978 0.594 0.5 0.98 0.5 0.98C0.5 0.98 0.022 0.594 0.022 0.388C0.022 0.183 0.236 0.017 0.5 0.017C0.764 0.017 0.978 0.183 0.978 0.388Z" /> </clipPath> </defs></svg>ポイントとしてはclipPathのclipPathUnits属性にobjectBoundingBoxを指定することです。これを指定することで座標が0〜1の相対値として扱われ、要素のサイズが変わっても形状が追従します。
またpathのd属性には、元のSVGのpath座標を相対値(0〜1)に変換した値を入れています。変換方法はシンプルで、x座標をviewBoxの幅(46)で、y座標をviewBoxの高さ(59)でそれぞれ割っています。
例えば M45 22.9455 は、x座標を46で、y座標を59で割って M0.978 0.388 になります。
まとめると次のようになります。
clipPathUnits="objectBoundingBox" | d属性の座標を0〜1に変換 | |
|---|---|---|
| 役割 | 座標系の宣言 | 座標値の変換 |
| 何をするか | 「このclipPathの座標は0〜1の相対値として解釈する」とブラウザに伝える | 実際のpath座標をその座標系に合わせた値に書き換える |
| 片方だけだとどうなるか | 座標系だけ変えても、座標値が46や59のままなので形が崩れる | 座標値を変換しても、座標系がデフォルト(ピクセル)のままなので追従しない |
そしてCSSではHTMLに配置したpathを参照しています。
.pin { width: 75px; aspect-ratio: 46 / 59; background-image: url(../img/common/icon_pin.svg); background-repeat: no-repeat; background-size: contain; backdrop-filter: blur(4px); clip-path: path( "M45 22.9455C45 35.0656 23 57.8 23 57.8C23 57.8 1 35.0656 1 22.9455C1 10.8253 10.8497 1 23 1C35.1503 1 45 10.8253 45 22.9455Z" ); clip-path: url(#pin-clip);}最終的に次のようになりました。
綺麗にパスに沿ってクリッピングすることができました。ボタンの幅をアイコンの元のサイズである46pxに合わせなくても崩れることはありません。
まとめ
backdrop-filterだけでは矩形にしかぼかしがかからない問題を、clipPathUnits="objectBoundingBox"を利用したインラインSVGのclipPathで解決することができました。
今回は地図ピンの形状で試しましたが、ロゴやアイコンなど任意のSVGパスに対しても同じ手法が使えます。仕組みを理解できれば形状を問わず応用できそうです。
なお、backdrop-filterとclip-path: url()はいずれもモダンブラウザで広くサポートされています。詳細な対応状況はCan I Use をご参照ください。
