mix-blend-modeで背景だけにブレンドモードを適用する方法

CSSのmix-blend-modeは、重なり合う要素のブレンドモードを指定するプロパティです。
このプロパティを使うと、単なる透過だけではなく、乗算などさまざまなブレンドモードで重ねることができるようになるので、デザインの幅もぐっと広がります。どんどん使っていきたいところなのですが、意外と制約が多くままならないことが多いので、実際のコーディング例をもとに気をつけるべきポイントをまとめてみました。

#コーディングするデザイン
画像の上に文字が乗っており、その下に座布団（塗りつぶした長方形）が重なっているという、よくあるデザイン。
座布団だけが乗算で重なっており、その上のテキストは不透明の白で乗っています。
画像は背景ではなく、img要素としてコーディングされる必要があると仮定します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1726777/62fc7eca-61ea-0802-08e1-2558ac4b1ad3.jpeg" width="350" height="250" alt="デザイン例">

#コーディングの失敗例その1：文字も乗算になってしまう

まずは以下のようにコーディングしてみます。

HTML

~~~html
<div class="wrapper">
    <div class="image">
        <img src="img/lunch.jpg" alt="ランチ">
    </div>
    <div class="text">
        <p class="title">Lunch</p>
    </div>
</div>
~~~

CSS

~~~css
  .wrapper {
    width: 700px;
    font-family: Baskerville;
    position: relative;
  }
  .wrapper .text {
    position: absolute;
    top: 50%;
    left: 50%;
    -webkit-transform: translate(-50%, -50%);
        -ms-transform: translate(-50%, -50%);
            transform: translate(-50%, -50%);
    background-color: rgba(69, 38, 18, 0.7);
    padding: 50px 110px;
  }
  .wrapper .title {
    font-size: 70px;
    color: #ffffff;
  }
~~~

表示
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1726777/97a14208-fc79-a08e-759a-8ffbd414a322.png" width="350" height="250" alt="スクリーンショット">
このコーディングの仕方だと、座布団にmix-blend-mode: multiplyを指定すると、その子要素であるテキストも乗算で重ねられてしまいます。

#コーディングの失敗例その2：乗算にならない

それでは、座布団の要素にbefore擬似要素を置いて、テキストとは兄弟関係になるようにしてみましょう。

CSS

~~~css
  .wrapper {
    width: 700px;
    font-family: Baskerville;
    position: relative;
  }
  .wrapper .text {
    position: absolute;
    top: 50%;
    left: 50%;
    -webkit-transform: translate(-50%, -50%);
        -ms-transform: translate(-50%, -50%);
            transform: translate(-50%, -50%);
    padding: 50px 110px;
  }
  .wrapper .text:before {
    content: '';
    display: block;
    position: absolute;
    z-index: 1;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(69, 38, 18, 0.7);
    mix-blend-mode: multiply; 
  }
  .wrapper .title {
    font-size: 70px;
    color: #ffffff;
    position: relative;
    z-index: 2;
  }
~~~
表示
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1726777/f89b33e8-7059-ab94-775e-dc630b724f22.png" width="350" height="250" alt="スクリーンショット">
少しわかりにくいかもしれませんが、座布団の要素.text:beforeに設定したmix-blend-mode: multiplyが適用されていないことが分かります。

##なぜmix-blend-modeが適用されないのか？
実は、mix-blend-modeは、同じスタックコンテキスト内にある要素同士のブレンドモードを指定するプロパティです。
このコーディング例では、座布団の要素に

~~~
transform: translate(-50%, -50%);
~~~

を指定しています。
実は、要素にtransformを指定すると、新しいスタックコンテキストが作られます。すると、その子要素であるbefore擬似要素とp要素は料理の写真のimg要素とは別のスタックコンテキストに属することになり、これらの要素の重なりには、mix-blend-modeが適用されなくなってしまうのです。

#成功例

これをもとに、もう少し考えてみます。
要素の重なりと、画像の中心にテキストを置く配置を別の方法で実装します。

~~~css
  .wrapper {
    width: 700px;
    font-family: Baskerville;
    position: relative;
    display: grid;
    grid-template-rows: auto;
    grid-template-columns: auto;
  }
  .wrapper .image {
    grid-row: 1 / 2;
    grid-column: 1 / 2;
  }
  .wrapper .text {
    grid-row: 1 / 2;
    grid-column: 1 / 2;
    align-self: center;
    justify-self: center;
    padding: 50px 110px;
    position: relative;
  }
  .wrapper .text:before {
    content: '';
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(69, 38, 18, 0.7);
    mix-blend-mode: multiply;
    z-index: 1;
  }
  .wrapper .title {
    font-size: 70px;
    color: #ffffff;
    position: relative;
    z-index: 2;
  }
~~~

この例では、ラッパ要素にdisplay: gridを指定しました。
そして、画像とテキストを同じグリッドセル上に配置します。
テキストの中央寄せは、

~~~
align-self: center;
justify-self: center;
~~~

で指定しました。

この時、画像とテキストの重なり順を指定しようと、z-indexを指定してしまわないように気をつけてください。
positionをstatic以外に設定し、かつz-indexがauto以外の時も、やはりスタックコンテキストが新たに作られてしまうからです。
CSS Gridでは、同じグリッドセル上にある要素は、ソースコード上で後に書かれたものが上に来る仕様になっています。上に置きたい要素を後の方に書くようにしましょう。

結果
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1726777/a4dc3b30-e739-0108-74fb-a944c939f1fd.png" width="350" height="250" alt="スクリーンショット">
デザイン通りの表示になりました。