Tech

【CSS】mix-blend-modeとbackdrop-filter(blur)を両立させる実装パターン

最終更新 公開

はじめに

Webサイトのヘッダーなどで、「背景色に合わせて文字色を反転させつつ(mix-blend-mode: difference)、背景にはすりガラスのようなぼかし(backdrop-filter: blur)を入れたい」と思ったことはありませんか?

弊社コーポレートサイトのヘッダーを実装する際、まさにこの両方を適用しようとしたのですが、ある問題に直面しました。

今回は、この2つのプロパティを共存させるための解決策をご紹介します。

気づいた点:2つを同じ要素につけると効かない?

実装を進める中で、「mix-blend-modebackdrop-filterを同じ要素に適用すると、mix-blend-modeが効かなくなってしまう」という仕様の壁にぶつかりました。
これはCSSの重ね合わせの順序の仕様によるものです。
backdrop-filterを適用した要素は、その要素自身が独立したレイヤーを作ってしまいます。そのため、その要素にmix-blend-modeをかけても、背後にあるページ全体のコンテンツと色がブレンドされなくなってしまうのです。

解決策:要素を2つに分けて「重ねる」

この問題を解決するために「blur(ぼかし)を担当する要素」と「mix-blend-mode(色反転)を担当する要素」をHTML上で完全に分離させ、それらを重ね合わせて表示するという方法をとります。

HTML
ヘッダーを2つのレイヤーに分けます。
1つは背面に配置する「ブラー(ぼかし)専用のダミーヘッダー」、もう1つは前面に配置する「色反転専用のメインヘッダー」です。

<!-- ① 背面:ブラー(ぼかし)を担当するレイヤー -->
<div class="header__blur" aria-hidden="true">
  <span class="header__logo__link"></span>
  <div class="header__arrow header__blur__arrow">
    <div class="header__arrow__text">
      <p>About</p>
      <span></span>
      <p>Recruit</p>
    </div>
    <img src="/img/arrow-down.svg" alt="">
  </div>
</div>

<!-- ② 前面:色反転(difference)を担当するレイヤー -->
<header class="header header--blend">
  <a href="/" class="header__logo__link">
    <img src="/img/logo_icon_white.svg" alt="">
  </a>
  <div class="header__arrow --about js-open">
    <div class="header__arrow__text --about">
      <p>About</p>
      <span></span>
      <p>Recruit</p>
    </div>
    <img src="/img/arrow-down.svg" alt="">
  </div>
</header>

SCSS
ポイントは、背面のダミー要素(.header__blur)のテキストや画像は visibility: hidden; で隠しつつ、幅や高さの空間だけを確保することです。
前面の要素(.header–blend)には mix-blend-mode: difference; をかけ、背景は透明にします。

/* 共通のヘッダーレイアウト */
.header, .header__blur {
  height: pxToRem(56px);
  position: fixed;
  display: flex;
  padding: pxToRem(14px) pxToRem(16px);
  gap: pxToRem(16px);
  align-items: center;
  
  @include mq(lg) {
    height: pxToRem(80px);
    padding: pxToRem(21px) pxToRem(24px);
    gap: pxToRem(24px);
  }
}

/* =========================================
   前面レイヤー:色反転(mix-blend-mode)担当
========================================= */
.header {
  z-index: 1000; /* 前面に配置 */
}

.header--blend {
  /* 背景に応じて全体を反転させる */
  mix-blend-mode: difference;
  
  .header__arrow {
    /* blurは持たせず、背面を透過させる */
    background: transparent;
    backdrop-filter: none;
  }
}

/* =========================================
   背面レイヤー:ぼかし(blur)担当
========================================= */
.header__blur {
  z-index: 999; /* 背面に配置 */
  pointer-events: none; /* クリック判定を無効化 */
  
  .header__blur__arrow {
    /* フロスト(すりガラス)背景だけを描画 */
    background: rgba(234, 232, 231, 0.02);
    backdrop-filter: blur(20px);
    border-color: transparent;
  }
  
  /* 中身は幅を合わせるために配置するが、描画はしない */
  .header__arrow__text,
  img {
    visibility: hidden;
  }
}

/* 各種パーツのスタイリング */
.header__arrow {
  padding: pxToRem(4px) pxToRem(16px);
  border-radius: 50px;
  border: 1px solid #fff;
  display: flex;
  align-items: center;
  box-shadow: 0 0 12px 0 transparent;
  cursor: pointer;
  transition: box-shadow 0.3s ease;

  img {
    display: block;
  }
  &:hover {
    box-shadow: 0 0 12px 0 rgba(77, 77, 77, 0.50);
  }
}
// ※以降、.header__arrow__text などのスタイル定義が続く...

おわりに

要素を2つに分けて重ねることで2つのプロパティを見事に共存させることができます。
同じ問題でつまずいた方の参考になれば幸いです!

こちらのCSSの記事も良ければ合わせてご確認ください。

方眼紙のような背景をCSSで作る

課題 方眼紙のような背景を画像で実装していたが、実機で拡大すると下記の通り途切れたように見える部分がある。 原因 ピクセルのズレと丸め誤差スマホの画面に合わせたり、ピンチアウトで拡大したりすると、ブラウザは画像を再計算して描画する。その際、「この1ピクセルの線は、画面上のどのピクセルに表示すべきか?」を計算するが、小数点以下のズレ(例えば「1.5ピクセル目に描画する」な