MENU

CSS で文字をすりガラスにする方法 ─ backdrop-filter + mask-image

🐧 採用ページに「すりガラス文字エフェクト」を実装したんだけど、Mac で確認したらフォントがぜんぜん違う!!
デザインでは Montserrat ExtraBold Italic なのに、Mac ではなんか違うゴシック体みたいになってる。

Windows・Android では完璧なのに Mac だけ再現しない、あの感じ。
結論から言うと、CSS の mask-image は SVG をレンダリングする際に隔離されたコンテキストを使うため、ドキュメントの Web フォントが読み込まれないことが原因だった。
解決策として、フォントを SVG に直接埋め込む方法を試したらうまくいったので、その手順をメモしておく!!

↑ backdrop-filter + mask-image のすりガラス文字エフェクト(フォント: Montserrat ExtraBold Italic)

目次

まず結論

フォントをサブセット化して WOFF2 → base64 にし、SVG の @font-face に直接埋め込む。
さらにその SVG 全体を base64 に変換して data:image/svg+xml;base64,... 形式で mask-image に渡す。

① fonttools でフォントをサブセット化

pip install fonttools brotli
import base64, io
from fontTools import ttLib, subset

font_path = "Montserrat-ExtraBoldItalic.ttf"
chars = "CREATE ENERGY"  # 使う文字だけサブセット化

ss = subset.Subsetter()
ss.populate(unicodes={ord(c) for c in chars})
font = ttLib.TTFont(font_path)
ss.subset(font)

buf = io.BytesIO()
font.flavor = "woff2"  # WOFF2 形式で出力
font.save(buf)
font_b64 = base64.b64encode(buf.getvalue()).decode("ascii")
print(f"サイズ: {len(buf.getvalue())} bytes")  # → 約 3,500 bytes

② SVG に @font-face を埋め込んで data URI に変換

style = (
    "<style>@font-face{"
    "font-family:'Montserrat';font-weight:800;font-style:italic;"
    f"src:url('data:font/woff2;base64,{font_b64}') format('woff2')"
    "}</style>"
)

svg = (
    '<svg width="1921" height="272" viewBox="0 0 1921 272"'
    ' xmlns="http://www.w3.org/2000/svg">'
    f"<defs>{style}</defs>"
    '<text fill="#fff" font-size="198" font-family="Montserrat"'
    ' font-weight="800" font-style="italic" letter-spacing=".04em"'
    ' transform="translate(15 207)">CREATE ENERGY</text>'
    "</svg>"
)

# SVG 全体を base64 にして data URI に変換
svg_b64 = base64.b64encode(svg.encode()).decode("ascii")
mask_uri = f'url("data:image/svg+xml;base64,{svg_b64}")'

③ SCSS で使う

.blur-text {
  /* Python で生成した base64 data URI を変数にセット */
  --_mask-svg: url("data:image/svg+xml;base64,AAEC...");

  backdrop-filter: blur(50px) brightness(1.2);
  mask-image: var(--_mask-svg);
  mask-size: contain;
  mask-repeat: no-repeat;
  mask-position: center;
}

そもそも何が起きてたか

実装したエフェクトはこんな仕組み。

  • mask-image に SVG テキストを渡して、文字の形でマスクを作る
  • マスクされた部分に backdrop-filter: blur(...) brightness(...) を適用
  • → 背景画像がすりガラス状にぼけて、文字の形に見える

この時の mask-image の値はデータ URI の SVG で、こんな感じ:

/* ❌ data URI だけど font-family で Web フォントを指定 */
.blur-text {
  backdrop-filter: blur(50px);
  mask-image: url("data:image/svg+xml,%3Csvg ...%3E%3Ctext font-family='Montserrat'%3ECREATE%3C/text%3E%3C/svg%3E");
}
/* → mask-image のレンダリングは隔離コンテキストなので
       ドキュメントの @font-face が読まれない
       Mac だとシステムフォントに化けてしまう */

Windows や Android では Montserrat がシステムにインストールされてるケースが多いのでたまたまうまくいってた。
でも Mac の場合はシステムフォントに Montserrat がないので、フォールバックフォントでレンダリングされてしまう。

なぜ mask-image の SVG でフォントが効かないのか

mask-image で参照される SVG は、ブラウザが隔離されたレンダリングコンテキストで処理する。
つまりドキュメント側で読み込んだ @font-face がそのコンテキストには届かない。

これは CSS の仕様上の制約で、外部リソース(Web フォント含む)へのアクセスが制限される。
セキュリティのためと考えるとまあ納得できる仕様。

試したけど NG だった方法

アプローチ結果理由
外部 SVG ファイルに @font-face を書くbackdrop-filter が壊れる(外部 SVG では動かない)
data URI の SVG に font-family 指定隔離コンテキストで Web フォントが読まれない
SVG をパスデータに変換して書くパスが正確じゃないと字形が崩れる・手間が大きい

外部 SVG ファイルに @font-face を書く方法をさらに詳しく説明すると:

/* ❌ 外部 SVG ファイルを参照 → backdrop-filter が壊れる */
.blur-text {
  backdrop-filter: blur(50px);
  mask-image: url("create-energy.svg");
}

/* ❌ SVG ファイル側に @font-face を書いても同じ */
/* → backdrop-filter が動かない or フォントが無視される */

外部 SVG だと backdrop-filter 自体が動作しなくなるブラウザが多い。
フォント問題より先に、エフェクト自体が壊れてしまうので NG。

解決策がなぜ動くのか

data URI の SVG 内に直接 @font-face を書いて、フォントもそこに base64 で埋め込んでしまえば外部リソースへのアクセスが不要になる。
隔離コンテキストでも自己完結したフォント情報が使えるので、どの OS・ブラウザでも同じフォントでレンダリングされる。

ただしフォントファイル全体を埋め込むとサイズが爆発するので、サブセット化が必須。
今回は「CREATE ENERGY」の9種類の文字だけに絞ったら、約 3.5KB に収まった!!

Mac 固有の罠:SVG の <filter> でイタリック体が切れる

フォント埋め込みで Mac のフォント問題は解決したんだけど、今度は「Y の右上が切れてる」問題が発生した。
原因は SVG の <filter> 要素に明示的な座標・サイズを指定していたこと。

<filter x="0" y="0" width="100%" height="100%"> のように範囲を明示すると、フィルタの出力範囲がその矩形にクリップされる。
イタリック体のグリフは右方向に張り出すのに、Mac(CoreText)は Windows より少しだけ広めにグリフをレンダリングする。
その差分でフィルタ領域をはみ出して切れてしまった。

解決策は単純で、mask-image 用の SVG ではフィルタを使う必要がないので <filter> 要素ごと削除した。
backdrop-filter は CSS 側(要素に直接)かけるので、SVG 側にはフィルタ不要!!

まとめ

  • mask-image の SVG は隔離コンテキストで動く → ドキュメントの Web フォントが使えない
  • backdrop-filter と一緒に使う場合は data URI 形式が必須(外部 SVG では backdrop-filter が壊れる)
  • 解決策:フォントをサブセット化 → WOFF2 → base64 → SVG の @font-face に埋め込み → SVG 全体を base64 で data URI に
  • fonttools の brotli も入れないと WOFF2 出力でエラーになる
  • SVG に <filter> を書く場合は明示的なクリップ領域に注意(Mac でイタリック体が切れやすい)
  • mask-image 用 SVG に backdrop-filter を持たせる必要はない → <filter> は削除でOK

参考

以上!!
誰かのお役に立てれば嬉しいです🐧

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

目次