😺

CSSのdisplayプロパティでフェードイン/アウトを実現する@starting-styleとtransition-behavior

2025/03/12に公開

CSSで display: none から display: block への切り替えをスムーズにアニメーションで行いたいケースは多いと思います。

たとえば、モーダルやポップオーバー、ドロップダウンメニューなどのUI要素を表示・非表示する際に、フェードインやフェードアウトのアニメーションを付けたいことがあります。
しかし、従来のCSSでは display プロパティはアニメーション可能なプロパティではなかったため、JavaScriptなどを使って実現する必要がありました。

今回、CSS Wrapped 2024@starting-styletransition-behavior を使って、CSSだけで display プロパティを使ったフェードイン・フェードアウトを実現する方法が紹介されていたので試してみました!
※ CSSだけと書いていますがこの記事ではHTMLへのclassの付与にJSを使っています。

@starting-styletransition-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 のトランジションが始まるという動作になります。
しかし、実際には displayopacity の変更が同時に適用されるため、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つの方法で使用できます。

  1. スタンドアロンブロックとして使用する方法
#box.show {
  display: block;
  opacity: 1;
  transition: opacity 0.5s;
}

@starting-style {
  #box.show {
    opacity: 0;
  }
}
  1. 既存のルールセット内にネストする方法
#box.show {
  display: block;
  opacity: 1;
  transition: opacity 0.5s;

  @starting-style {
    opacity: 0;
  }
}

どちらの方法でも、要素がはじめて表示される際に opacity: 0 から opacity: 1 へのトランジションが適用されます。

実装状況

現在、主要なモダンブラウザでサポートされていますが、Firefoxは display: none の要素には適用されない部分的サポートで、baselineではLimited availabilityになっています。

https://developer.mozilla.org/ja/docs/Web/CSS/@starting-style

そのままフェードアウトはうまくいかない?

@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になっています。

https://developer.mozilla.org/ja/docs/Web/CSS/transition-behavior

まとめ

@starting-styletransition-behavior を組み合わせることで、CSSだけで display プロパティを使ったフェードイン・フェードアウトを実現できるようになりました!

CSSを書いたことがある人は、一度はやろうと思ってがっかりしたことがあると思うので待望の機能ですね!

@starting-styletransition-behavior の動作原理を知っておけば、今回紹介したフェードイン・フェードアウト以外にも応用できる機会は多そうなので、しっかり覚えておきたいです。

RightTouch

Discussion