CSSのdisplayプロパティでフェードイン/アウトを実現する@starting-styleとtransition-behavior
CSSで display: none
から display: block
への切り替えをスムーズにアニメーションで行いたいケースは多いと思います。
たとえば、モーダルやポップオーバー、ドロップダウンメニューなどのUI要素を表示・非表示する際に、フェードインやフェードアウトのアニメーションを付けたいことがあります。
しかし、従来のCSSでは display
プロパティはアニメーション可能なプロパティではなかったため、JavaScriptなどを使って実現する必要がありました。
今回、CSS Wrapped 2024で @starting-style
と transition-behavior
を使って、CSSだけで display
プロパティを使ったフェードイン・フェードアウトを実現する方法が紹介されていたので試してみました!
※ CSSだけと書いていますがこの記事ではHTMLへのclassの付与にJSを使っています。
@starting-style
と transition-behavior
を使わない場合の問題点
まずは、従来の方法で display
プロパティを切り替える際の問題点を見てみます。
以下のようなシンプルなHTML・CSS・JSがあるとします。
<button id="toggle-btn">表示/非表示</button>
<div id="box">こんにちは!</div>
<p>hogehoge</p>
#box {
width: 200px;
height: 100px;
background-color: skyblue;
display: none;
opacity: 0;
transition: opacity 0.5s;
}
#box.show {
display: block;
opacity: 1;
}
document.getElementById('toggle-btn').addEventListener('click', () => {
document.getElementById('box').classList.toggle('show');
});
このコードでは、ボタンをクリックすると #box
要素に .show
クラスを追加して表示させる想定です。
transition: opacity 0.5s
が設定されていますが、実際にこのコードを動かしてみると、フェードインアニメーションは動作せず、要素が突然表示されてしまいます。
なぜアニメーションしないのか?
これは、display
プロパティが「離散的なプロパティ」であるためです。
CSSのトランジションは、数値や色などの「補間可能なプロパティ」に対してのみ機能します。display: none
から display: block
への変更は瞬時に行われ、その間の中間状態は存在しません。
そのため、display: none
から display: block
に変わった瞬間に要素が表示され、その後に opacity
のトランジションが始まるという動作になります。
しかし、実際には display
と opacity
の変更が同時に適用されるため、opacity
のトランジションは効果がありません。
visibility: hidden
を使う方法
従来の回避策として、visibility: hidden
を使う方法があります。
visibility
プロパティは display
と違い、要素の空間を保持したまま非表示にできます。
.box {
width: 200px;
height: 100px;
background-color: skyblue;
visibility: hidden;
opacity: 0;
transition: opacity 0.5s, visibility 0.5s;
}
.box.show {
visibility: visible;
opacity: 1;
}
この方法では、フェードインアニメーションは実現できますが、非表示時も要素のスペースが確保されたままになるという問題があり、レイアウトに影響を与えたくない場合には適していません。
@starting-style
とは?
@starting-style
は、要素がはじめて表示される際や display: none
から他の値に変わる際の初期スタイルを定義するためのCSS at-ruleです。
これにより、要素が最初に表示される際のトランジションの「開始状態」を指定できます。
基本的な使い方
@starting-style
は、次の2つの方法で使用できます。
- スタンドアロンブロックとして使用する方法
#box.show {
display: block;
opacity: 1;
transition: opacity 0.5s;
}
@starting-style {
#box.show {
opacity: 0;
}
}
- 既存のルールセット内にネストする方法
#box.show {
display: block;
opacity: 1;
transition: opacity 0.5s;
@starting-style {
opacity: 0;
}
}
どちらの方法でも、要素がはじめて表示される際に opacity: 0
から opacity: 1
へのトランジションが適用されます。
実装状況
現在、主要なモダンブラウザでサポートされていますが、Firefoxは display: none
の要素には適用されない部分的サポートで、baselineではLimited availabilityになっています。
そのままフェードアウトはうまくいかない?
@starting-style
だけでは、フェードアウトアニメーションはうまく動作しません。
これは、@starting-style
が要素の初期表示時のスタイルのみを定義するためです。要素を非表示にする際のアニメーションには、次に説明する transition-behavior
プロパティが必要になります。
transition-behavior
とは?
transition-behavior
は、離散的なプロパティ(display
など)に対してトランジションを適用するかどうかを指定するプロパティです。デフォルト値は normal
で、離散的なプロパティにはトランジションが適用されません。
transition-behavior: allow-discrete;
を指定することで、離散的なプロパティにもトランジションを適用できるようになり、display
プロパティの変更がトランジションの終了時(フェードアウト時)または開始時(フェードイン時)に適用されるようになります。
具体的には
-
display: none
からdisplay: block
に変更する場合、トランジションの開始時(0%)にdisplay: block
が適用されます -
display: block
からdisplay: none
に変更する場合、トランジションの終了時(100%)にdisplay: none
が適用されます
これにより、フェードアウト時に要素が完全に透明になってから非表示になるという自然な動作が実現できます。
#box {
width: 200px;
height: 100px;
background-color: skyblue;
display: none;
opacity: 0;
transition: opacity 0.5s, display 0.5s;
transition-behavior: allow-discrete;
}
#box.show {
display: block;
opacity: 1;
@starting-style {
opacity: 0;
}
}
実装状況
transition-behavior
プロパティも比較的新しい機能ですが、2024年に主要なモダンブラウザで実装され、baselineではNewly availableになっています。
まとめ
@starting-style
と transition-behavior
を組み合わせることで、CSSだけで display
プロパティを使ったフェードイン・フェードアウトを実現できるようになりました!
CSSを書いたことがある人は、一度はやろうと思ってがっかりしたことがあると思うので待望の機能ですね!
@starting-style
と transition-behavior
の動作原理を知っておけば、今回紹介したフェードイン・フェードアウト以外にも応用できる機会は多そうなので、しっかり覚えておきたいです。
Discussion