全ブラウザ/レスポンシブデザイン対応、HTMLの入れ子を増やさずにCSSで背景画像を半透明にする方法

用途

  • divとかのボックスに半透明の背景画像を付けたい。
  • ただし中の要素まで半透明にされると困る。
  • レスポンシブ対応で、ボックスの縦横比が変わっても背景画像はボックス全体をカバーするように。

課題

背景画像を設定し、普通にopacityをかけると中の要素まで半透明になって困る。

解決法

考え方

背景画像を設定したボックスにopacityを付けると、子要素まで半透明になるのなら、子要素の入らないbefore疑似要素に背景画像を設定すればいい。
before疑似要素をposition: absolute;を使ってボックス全体をカバーすれば目的は達成できる。
なお、after疑似要素でも同じことができるけど、ここはclearfixが使えるように開けておきたい。

コード構成

HTML

<div class="background-opacity">
  コンテンツ類
</div>

CSS

.background-opacity {
  position: relative;
  z-index: 0;
}
.background-opacity::before {
  content: " ";
  display: block;
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  background-image: url(背景画像のパス);
  background-size: cover;
  background-position: center center;
  opacity: 0.35;
}
.background-opacity * {
  position: relative;
}

コード解説

.background-opacity::before

まず、contentプロパティにスペースを入れることでbefore疑似要素を有効にします。。

  content: " ";

次に、before疑似要素を親要素である.background-opacityの領域全体に広げます。

  display: block;
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;

display: block;でwidth, heightプロパティを使えるようにしています。
これはブロックレベル要素であればよいので、display: inline-block;やdisplay: table;でも同じような動作をしてくれるはずです。
そしてposition: absolute;を使って、.background-opacityの子要素がbefore疑似要素に重複するようにします。
また、top と left プロパティは、デフォルトではbefore疑似要素が.background-opacityのpaddingなどの影響を受けてしまうので、0の値を入れます。

最後にメインディッシュの背景画像の半透明化です。

  background-image: url(背景画像のパス);
  background-size: cover;
  background-position: center center;
  opacity: 0.35;

背景画像は、画像のボケを許容する場合はボックスより小さいサイズでもOKです。ボケを発生させたくない場合は、画像の高さ、幅とも、想定される最大値を超えるようにしなければなりません。つまり、パソコンの画面で想定される最大幅以上の幅、スマホの縦長のサイズにしたときの最大の高さ以上の高さとなる画像を用意します。

background-sizeは、基本的にcoverを使って、ボックスからあふれる分はトリミングされるようにしています。デザインによっては他の値を入れてもよいでしょう。

background-positionは、今回の例では画像の中央が常に表示され、端の方はトリミングされてもOK,という形になっています。写真によって配置場所は変えるとよいでしょう。

最後に、opacityで写真の透明度を設定し、before疑似要素の調整は終了です。

.background-opacity

これはbefore疑似要素の親要素になるため、position:relative;を設定する必要があります。

  position: relative;
  z-index: 0;

z-index: 0;の設定が地味に重要で、デフォルトだとbefore疑似要素が他の子要素の上を覆ってしまいます。

.background-opacity *
  position: relative;

最後に、.background-opacity の子要素全てにposition: relative;を設定します。
この設定にはz-index: 1;が隠れています。(デフォルトの設定)
これで全ての子要素がbefore疑似要素より上に配置されます。

カラーフィルター効果への応用

試してはいませんが、背景画像を.background-opacityに設置、before疑似要素にbackground-colorをrgbaでセットすると、背景画像へのカラーフィルター効果が得られるはずです。もしかしたらz-indexの値を.background-opacityは0、before疑似要素は1、.background-opacity * には2を設定する必要があるかもしれません。

この方法の利点と欠点について

利点

HTMLの入れ子が増えないので、HTML側のメンテナンス性が上がります。
rgbaを使わず、シンプルにopacityで透明度をコントロールできます。
なんとなくハック感があって書いてて楽しい。

欠点

子要素のz-indexを制御するための設定で詳細度を上げている(しかもユニバーサルセレクタを使っている)ため、子要素内でpositionプロパティを使ったレイアウトを使う場合にはほぼ確実に干渉する。その場合は素直にHTMLを入れ子にして半透明フィルタをかける方がよいでしょう。
またCSSのコード量が、HTMLを入れ子にする方式より増えます。
疑似要素やpositionプロパティを使い慣れない初級のコーダーにとっては難解なコードとなる恐れもあります。誰でもメンテナンスできるように、という観点からはあまり好ましくない方法かもしれません。

まとめ

現状のCSSの仕様で簡単にコーディングできないデザインを実現するのは、コーダーの腕の見せ所でありやってて楽しいポイントでもあります。
しかし簡単に実現できないデザインである以上、HTMLかCSSか、あるいはその両者にしわ寄せが行き、コード量の増大、可読性、メンテナンス性の低下を招きます。その負担をHTML側にするのか、CSS側にするのかはコーダーの価値観やサイトの使われ方によって判断が分かれます。
今回の方法はCSS側が負担する手法として使ってもらえればと思います。