ZATYのBLOG

お問い合わせする

【CSS】blurがうまく効かない!3つの罠と解決方法

要素のぼかしに使うfilter: blur();背景ぼかしに使うbackdrop-filter: blur();を使っていると、思わぬ挙動をすることがあります。

今回はblurでハマりがちな問題と解決方法を解説します。

※ コード例とサンプルプレビューはブログの埋め込み都合上異なることがあります。

filterとbackdrop-filterのblur()

まずは、CSSプロパティを簡単に解説します。

filter: blur();

このプロパティは要素自体をぼかすことができます。

<style>
.filter {
  filter: blur(2px);
}
</style>
<p class="filter">ぼやけた文章</p>

ぼやけた文章

backdrop-filter: blur();

<style>
.backdrop-filter {
  position: absolute;
  inset: 5%;
  backdrop-filter: blur(10px);
}
</style>
<div class="sample-block">
  <img src="example.png" />
  <p class="backdrop-filter">背景がぼやける要素</p>
</div>

背景がぼやける要素

blurが祖先要素があるposition: fixedの罠

position:fixedは画面全体に固定表示させる時に使用するプロパティです。画面にくっついたヘッダーや上に戻るボタンなどでよく使用します。

filter: blur()backdrop-filter: blur()どちらかを指定した要素の下でfixedを使うと、blurを指定した祖先要素が基準で表示されてしまいます。

まずは通常のposition: fixedの挙動を確認します。

<style>
.fixed {
  position: fixed;
  top: 10px;
  left: 10px;
  padding: 8px;
  background-color: skyblue;
}
</style>
<div class="fixed">固定された要素</div>

固定された要素

画面の左上に固定された要素が表示されます。上下の基準が画面になっていることがわかります。

実際にうまくいかないサンプル

次に親要素にblurが指定されている時は、blurを指定した要素が基準になってしまう例を見てみます。

まずは、filter: blur()の例

<div class="sample-block">
  <div class="filter">
    <div class="fixed">固定された要素</div>
    <p>テキスト</p>
    <p>テキスト</p>
  </div>
</div>
固定された要素

テキスト

テキスト

同じように指定したfixedの要素が画面を基準ではなく、親要素が基準になってしまっているのがわかります。

次にbackdrop-filter: blur()の例

<div class="sample-block">
  <img src="example.png" />
  <div class="backdrop-filter">
    <div class="fixed">固定された要素</div>
    <p>テキスト</p>
    <p>テキスト</p>
  </div>
</div>
固定された要素

テキスト

テキスト

こちらも画面基準ではなくblurを指定した要素が基準になってしまっていることがわかります。

blur要素内のDOMに対してfixedを効かせることは現状できません。fixedさせたい要素をブラー要素の外に設置するしかありません。

背景blurの要素からモーダルを起動するときに、この挙動によく衝突します。今回はそのパターンの回避方法を紹介します。

ポータルを使った回避方法

ReactやVueを使っている時、コンポーネント内にモーダルコンポーネントを起動したい状況がよくあります。

<BlurBox>
  <Button />
  <Modal>
    <p>モーダル</p>
  </Modal>
</BlurBox>

ブラーのコンポーネント外にモーダルコンポーネントを設置することができれば解決できますが、コンポーネントの仕様上不可能であることも多々あります。

そんな時は、ポータルと呼ばれる仕組みを利用することで、ブラー要素の中に設置しながらfixedの挙動をさせることができます。

ReactではPortal、Vueではteleport(Vue3以前ではモジュールを利用する必要があります)と呼ばれる仕組みを利用します。これらはどちらもコンポーネント内に記述しながら、描画の際はあらかじめ指定した要素にモーダルのHTMLを設置するという仕組みになっています。

JSフレームワークでは、一般的に、#root要素のなかで描画をします。ポータルを使う場合は、#root要素の下(body直前が一般的)に#portalRootのような要素を作成し、このポータル要素を描画するときは、この要素の中に描画するという仕組みです。

<body>
  <div id="root">
    <!-- ここに普通のコンポーネントが表示 -->
  </div>
  <div id="portalRoot">
    <!-- ポータル要素はここに表示させる -->
  </div>
</body>

この仕組みを使うことでfilter: blur()の要素の外にposition: fixedの要素を移動させることができます。

それぞれの詳しい使い方は下記を参考にしてください。

Reactでポータルを使う

Vueでポータルを使う

Vueの3.0系以前でポータルを使う

ポータルはモーダルだけに限らず、エラーや成功状態を表示するトースト、一定部分だけ画面追従するスティッキーナビゲーションダイアログにも活用することができます。

もし、ReactやVueのフレームワークを使用せずにポータルを使いたい場合も、JavaScriptで同様のアプローチの処理を作ることで回避可能です。

blurが祖先要素にあるときのmix-blend-modeの罠

mix-blend-modeは指定した要素のテキストの文字色が背景色を考慮した色になるプロパティです。

<style>
.background {
  background-image: linear-gradient(90deg, black, white);
}
.blend-text {
  color: #fff;
  mix-blend-mode: difference;
}
</style>

いろはにほへと ちりぬるを

左から黒→白の背景グラデーションにかけて、文字色が反転しているのがわかります。

いろはにほへと ちりぬるを

画像の上にmix-blend-modeの文字を配置すると画像の色の反転色になっているのがわかります。

実際にうまくいかないサンプル

こちらも祖先要素にblur要素があると、このmix-blend-modeの処理が変わってしまいます。

まずは親要素にfilterがある例です。

<div class="color-block">
  <div class="filter">
    <p class="blend-text">親要素がぼやけた文字</p>
  </div>
</div>

ぼやけた文章

filter: blurの影響でぼやけていますが、文字色は反対色になっていないことがわかります。

次にbackdrop-filterの例です。

<div class="sample-block">
  <img src="example.png" />
  <div class="backdrop-filter">
    <p class="blend-text">背景がぼやける要素</p>
  </div>
</div>

背景がぼやける要素

こちらもmix-blend-modeをつけているのに、白系背景の上に白文字が乗ってしまっています。

回避方法

一番単純な解決法として、blurを設定している要素とmix-blend-modeを設定する要素を同一にすることでmix-blend-modeの予期しない挙動を回避することができます。

<div class="color-block">
  <p class="filter blend-text">親要素がぼやけた文字</p>
</div>
<div class="sample-block">
  <img src="example.png" />
  <p class="backdrop-filter blend-text">背景がぼやける要素</p>
</div>

ぼやけた文章

ぼやけた文章がちゃんと反転していることがわかります。

背景がぼやける要素

2025/02/15追記

上の修正ではSafariで正常に挙動しないことが判明しました。現在原因調査中です。

Safariでbackdrop-filterを使うとき

Safariでbackdrop-filterプロパティの挙動を使う場合はベンダープレフィックスを使う必要があります。

.complete-blur {
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}