👻

Tailwindのopacityが子要素に乗ってしまうのを防ぐ方法

に公開

はじめに

こんにちは!PortalKeyの渋谷です。


皆さんはこのような画像の上に文字を乗せたい時に

このようなフィルターをかけ

このように要素を乗せたい時ありませんか?

そしてこうなって困っていたりしませんか?

自分はこれに悩まされた過去があり、解決方法はわりと簡単だったのでまとめておきたいと思います。

開発環境

  • TypeScript v5.5.4
  • react v18.3.1
  • tailwindcss v3.4.10

なぜこうなったのか

まずはコードを確認してみましょう。

<div className="relative w-64 h-64">
  <img src="https://storage.googleapis.com/zenn-user-upload/avatar/36cbd3e131.jpeg" />
  <div className="absolute p-2 inset-0">
    <div className="w-full h-full p-1 flex flex-col gap-2 justify-center items-center bg-gray-200 opacity-50 rounded">
      <div className="text-white text-lg font-bold text-center whitespace-pre-line cursor-default">{"PortalKey\nあぁPortalKey\nPortalKey"}</div>
      <div className="px-4 py-1 text-white text-lg font-bold bg-lightgreen-500 hover:brightness-110 rounded-full cursor-pointer">Click here!</div>
    </div>
  </div>
</div>


結果
画像の上にabsoluteで要素を重ねています。
その要素の一番上でbg-gray-200に対してopacity-50を設定することでフィルターを表現しています。
それ以降opacityの指定はしていないのになぜ中のテキストやボタンまでが透明になってしまうのでしょうか…

実はopacityはそのレイヤーに対してのパラメータなので子要素全てに反映されてしまいます。
Tailwindに限らず、CSSでも同じような挙動になる仕様です。

じゃあ子要素でopacity-100して上書きしてあげればいいんじゃない?

という事で試してみます。

<div className="relative w-64 h-64">
  <img src="https://storage.googleapis.com/zenn-user-upload/avatar/36cbd3e131.jpeg" />
  <div className="absolute p-2 inset-0">
    <div className="w-full h-full p-1 bg-gray-200 opacity-50 rounded">
      <div className="w-full h-full flex flex-col gap-2 justify-center items-center opacity-100">
        <div className="text-white text-lg font-bold text-center whitespace-pre-line cursor-default">{"PortalKey\nあぁPortalKey\nPortalKey"}</div>
        <div className="px-4 py-1 text-white text-lg font-bold bg-lightgreen-500 hover:brightness-110 rounded-full cursor-pointer">Click here!</div>
      </div>
    </div>
  </div>
</div>


結果
なんと変わりません…
これも仕様なのです、opacityは上書きではなく乗算されるのが原因です。

じゃあどうすればいいのか

解決方法はとてもシンプルです。なぜならそれ用に用意されているパラメータがあるからです。
https://v2.tailwindcss.com/docs/background-opacity

試してみましょう。opacity-50をbg-opacity-50に置き換えるだけです。

<div className="relative w-64 h-64">
  <img src="https://storage.googleapis.com/zenn-user-upload/avatar/36cbd3e131.jpeg" />
  <div className="absolute p-2 inset-0">
    <div className="w-full h-full p-1 flex flex-col gap-2 justify-center items-center bg-gray-200 bg-opacity-50 rounded">
      <div className="text-white text-lg font-bold text-center whitespace-pre-line cursor-default">{"PortalKey\nあぁPortalKey\nPortalKey"}</div>
      <div className="px-4 py-1 text-white text-lg font-bold bg-lightgreen-500 hover:brightness-110 rounded-full cursor-pointer">Click here!</div>
    </div>
  </div>
</div>


結果
このパラメータは指定されたbg-colorに対してのopacity設定なので子要素に影響することもありません。
おまけに簡易的な書き方としてこんな書き方もできます。

<div className="w-full h-full p-1 flex flex-col gap-2 justify-center items-center bg-gray-200/50 rounded">

こっちの方がわかりやすい気もしますね。

おまけ

実は最初bg-opacityの存在に気づいておらず、力技で解決してました。
おまけとしてそちらも紹介しておきます。

1.レイヤー分けちゃう作戦

<div className="relative w-64 h-64">
  <img src="https://storage.googleapis.com/zenn-user-upload/avatar/36cbd3e131.jpeg" />
  <div className="absolute p-2 inset-0">
    <div className="relative w-full h-full rounded overflow-hidden">
      <div className="w-full h-full bg-gray-200 opacity-50" />
      <div className="absolute inset-0 p-1 flex flex-col gap-2 justify-center items-center ">
        <div className="text-white text-lg font-bold text-center whitespace-pre-line cursor-default">{"PortalKey\nあぁPortalKey\nPortalKey"}</div>
        <div className="px-4 py-1 text-white text-lg font-bold bg-lightgreen-500 hover:brightness-110 rounded-full cursor-pointer">Click here!</div>
      </div>
    </div>
  </div>
</div>

子要素をabsoluteにしてレイヤーを分け、opacityの影響下から逃がしていました。
まぁこれでもいいんですけど親要素に影響を与えたくなった時に困ってしまいますね…。

2.beforeで出しつつ子要素のレイヤーも分けちゃう作戦

<div className="relative w-64 h-64">
  <img src="https://storage.googleapis.com/zenn-user-upload/avatar/36cbd3e131.jpeg" />
  <div className="absolute p-2 inset-0">
    <div className="relative w-full h-full p-1 rounded overflow-hidden before:content-[''] before:absolute before:inset-0 before:bg-gray-200 before:opacity-50">
      <div className="absolute inset-0 flex flex-col gap-2 justify-center items-center">
        <div className="text-white text-lg font-bold text-center whitespace-pre-line cursor-default">{"PortalKey\nあぁPortalKey\nPortalKey"}</div>
        <div className="px-4 py-1 text-white text-lg font-bold bg-lightgreen-500 hover:brightness-110 rounded-full cursor-pointer">Click here!</div>
      </div>
    </div>
  </div>
</div>

これでも一応できますね。
子要素もレイヤー分けないとbeforeで表示したものが上に表示されちゃうので苦肉の策です。
こんなのやるくらいなら1の方が全然いいですが一応参考までに…

最後に

いかがでしたでしょうか?
まず新しいことやりたくなったらそれを実現できるパラメータが無いか確認するのを怠らないようにするのが一番大事ですね。
HTMLは書き方の作法が沢山ある影響で、他の方法でも動いてしまうのは良いところでもあるし悪いところでもありますね…
恐らく自分のコードもパッと見動いてるけど適した書き方できてないよ~って部分たくさんあるだろうなぁと思ってます…細かいところから直していきたいですね…!
ではまた!

PortalKey Tech Blog

Discussion