opacityとスタッキングコンテキストに立ち向かう

掲載日

スタッキングコンテキスト(重ね合わせコンテキスト)とは

重ね合わせコンテキスト (Stacking context) は、ビューポートまたはウェブページに面していると想定されるユーザーに対する仮想的な Z 軸に沿って並べられた HTML 要素の三次元の概念化です。

MDNより引用

cssでは、z-indexfloatposition等を使う時に、要素同士のどっちを前面に表示するか決めるために内部的にはz軸を持っているというお話です。

このz軸は、特定のスタイルを持っていない場合は概念が存在しない(要素は一番下に張り付く)のですが、opacity等を設定するとこの概念が適用されて要素が浮かび上がります。

以下のようなHTMLを作ってブラウザで開き、「要素1」と「要素2」が重なる位置にカーソルを合わせると、添付gifのようなチラつきが起こります。(点滅注意)

スタッキングコンテキストによってちらつきが発生する検証のGIF動画。
要素1と要素2が重なる位置にカーソルを当てるとちらつきが起きる
<!DOCTYPE html>
<html>

<head>
    <style>
        .element:hover {
            opacity: 0.6;
        }

        .element>div {
            position: fixed;
            color: white;
            width: 100px;
            height: 100px;
        }

        .element1>div {
            top: 50px;
            left: 0;
            background-color: rgb(255, 75, 0, 1);
            z-index: 2;
        }

        .element2>div {
            top: 100px;
            left: 50px;
            text-align: end;
            background-color: rgb(0, 90, 255, 1);
            z-index: 99;
        }
    </style>
</head>

<body>
    <p>
        要素1と要素2が重なる位置にカーソルを当てるとスタッキングコンテクストによってちらつきが起きる
    </p>
    <div class="element element1">
        <div>
            要素1(z-index:2)
        </div>
    </div>
    <div class="element element2">
        <div>
            要素2(z-index:99)
        </div>
    </div>
</body>

</html>

その原因がこのスタッキングコンテキストまわりの仕様にあり、以下のような動作をします。

  1. hover時に要素2にopacityが設定されるため、親要素が浮かび上がる。
  2. 要素2の親要素は z-index:2の要素1に負けて後ろに回る。
  3. 要素2のhoverが解除され、要素2のopacityスタイルが削除される。
  4. 再び要素2の方が上だと判定され、hoverされる。
  5. 1に戻り、hoverイベントが評価されなくなるまでループ。(チラつく。)


解決まで苦しめられたのでその備忘録です。

対策

案1. 親要素にposition: static以外z-indexを設定する

(※ サンプルのz-indexの値はテストなので適当にデカい数字を入れてますが、実際にやるときはちゃんと計算して設定しましょう。)

それまで浮かんでいなかった親要素が浮き上がるのが問題なので、最初から浮かせておこうという解決策。
浮き上がる親要素に最強のz-indexを設定すれば必ず一番上に来るので固定できます。


.element:hover {
     opacity: 0.6;
     position: relative; /* ここを追加 */
     z-index: 99999; /* ここを追加 */
 }

position指定入れてしまうこと自体がNG(表示位置が変わってしまう等)なケースもあるので注意。

案2. opacityの位置を合わせる

親要素を浮かせない解決をしたい時はこっちになるのかなぁと。
元からz-indexの存在する子要素でhover時にopacityを付けるようにしてもOKです。


.element>div:hover { /* ここを修正 */
    opacity: 0.6;
}

ただ、これもケースバイケースで、親要素の中に他の要素があるとその部分は透過しなくなるのでNGだったりすると思います。

もっといい解決策はありそうですが、自分はとりあえずこの二つでなんとかしました。

(おまけ)Edgeで3Dで要素の重なりが見れる

Edge ブラウザでは開発者ツールで3Dで要素の重なりを見ることができます

Microsoft Edgeブラウザで、3D View機能を使って要素の重なりを三次元で確認しているスクリーンショット。

添付のように、要素1が要素2の上に浮き上がってきていることが視覚的に分かりやすくみられます。

記事ではシンプルな構成にしているのでこんなリッチな機能を使わずとも構いませんが、実際の画面はもっと複雑な子孫要素があり、重なった状態になっていると思うので、今回のようなスタイルのちらつきや、それ以外でもスタッキングコンテキストが複雑に絡んでいるレイアウトで悩んでるときの調査に役に立ちそうです。

参考リンク

記事の作成者のA.W.のアイコン

この記事を書いた人

A.W.
茨城県在住Webエンジニアです。 PHP、PostgreSQL、Linuxなどを業務で使用しています。 趣味ではGoやNuxt、Flutterをやってます。

Comment

関連記事

check コピーしました!