CSSでdetails要素に開閉アニメーションを追加する方法

HTML5 で追加された details要素 (詳細折りたたみ要素) に開閉アニメーションを設定するには JavaScript が必要でしたが、現在は CSS のみで設定することができるようになっています。今回は、その方法を紹介します。

transition-behaviorプロパティの使用

これまで、transitionプロパティによってアニメーションできるのは、色や長さなどを設定する、アニメーション可能 (animatable) な CSSプロパティ に限られていました。しかし、transition-behavior: allow-discrete; を設定することで、それ以外のプロパティでもアニメーションが設定できるようになります。

details {
  & > summary {
    border-block-end: solid 1px;
    transition-property: border-block-end-style;
    transition-duration: 0s; /* アニメーション開始時に表示 */
    transition-behavior: allow-discrete;
  }
  &:not([open]) > summary {
    transition-delay: 0.5s; /* 0.5秒遅れてアニメーション開始 */
  }
}

上記の CSSコード では summary要素 にボーダーを設定しています。details要素 が開けられたとき、0秒でボーダーが表示されます。閉じられるときは、0.5秒遅れてボーダーが消えます。このように表示される時間を細かく設定することができるようになります。

ただし、FireFox は transition-behavior: allow-discrete; を完全にサポートしておらず、content-visibilityプロパティdisplayプロパティ では機能しないため、これらを使用したい場合には工夫が必要になります。

::details-content擬似要素の使用

details要素 と summary要素 のスタイリングも地味に面倒でした。

<style>
details {
  border: solid 1px;
  padding-inline: 2rem;

  & > summary {
    margin-inline: -2rem; /* details要素 の padding-inline を相殺 */
    padding-block: 1rem;
    padding-inline: 2rem;
  }
  &[open] { /* 開いているときだけボーダーと余白を追加 */
    padding-block-end: 2rem;

    & > summary {
      border-block-end: solid 1px;
      margin-block-end: 2rem:
    }
  }
}
</style>
…
<details>
  <summary> … </summary>
  <p> … </p>
  <p> … </p>
</details>

スタイリングのしやすさとデザインの自由度を高めるために、summary要素 以外を div要素 などでラップするのが一般的です。上記のコードと下記のコードは同じように表示されますが、div要素 でラップした下記の方が CSSコード はシンプルです。

<style>
details {
  border: solid 1px;

  & > summary {
    padding-block: 1rem;
    padding-inline: 2rem;
  }
  & > .content { /* 閉じているときは表示されない */
    border-block-start: solid 1px;
    padding: 2rem;
  }
}
</style>
…
<details>
  <summary> … </summary>
  <div class="content">
    <p> … </p>
    <p> … </p>
  </div>
</details>

しかし、::details-content擬似要素 の登場によって div要素 でラップする必要もなくなりました。

<style>
details {
  border: solid 1px;

  & > summary {
    padding-block: 1rem;
    padding-inline: 2rem;
  }
  &[open]::details-content {
    border-block-start: solid 1px;
    padding: 2rem;
  }
}
</style>
…
<details>
  <summary> … </summary>
  <p> … </p>
  <p> … </p>
</details>

注意点として、::details-content擬似要素 は details要素 が閉じているときでも非表示になりません。そのため、開いている状態 ([open]::details-content) の時だけ borderプロパティ や paddingプロパティ を設定するといった工夫が必要になります。

開閉アニメーションの設定

上記のことを踏まえ transition-behaviorプロパティ と ::details-content擬似要素 を組み合わせてアニメーションを設定します。

ポイントは FireFox では機能しない content-visibilityプロパティに visible を設定することです。これで開閉状態を問わず表示される状態になります。次に、閉じるとき visibility: hidden; を設定することで非表示状態を作ります。これで滑らかなアニメーションが設定できるようになります。

次に開閉アニメーションですが、::details-content擬似要素 の高さ (block-size) を 0px から autoauto から 0px に切り替えます。将来的には Chrome系ブラウザ が先行実装している interpolate-sizeプロパティ によって、auto, min-content, max-content, fit-content といったキーワード値でアニメーションが設定できるようになると思うのですが、これらは、グリッドレイアウトの 0fr1fr で代替可能です。

<style>
details {
  --transition-time: 0.5s; /* details要素 と ::details-content擬似要素 に設定するアニメーション時間 */

  border: solid 1px;
  display: grid;
    grid: auto 1fr / 1fr; /* summary要素 の高さは auto、::details-content擬似要素 の高さは 1fr */
    align-content: start; /* アニメーションの基点を上部に揃える */
    align-items: start; /* デフォルトの align-items: stretch; を無効化 */
  overflow: hidden; /* これが無いとアニメーション時に消えない */

  @media (prefers-reduced-motion: no-preference) { /* アニメーションが OFF の環境でなければ設定 */
    transition: grid var(--transition-time);
  }

  & > summary {
    padding-block: 1rem;
    padding-inline: 2rem;
  }
  &::details-content {
    border-block-start: solid 1px;
    content-visibility: visible; /* content-visibility: hidden; による非表示を無効化 */
    overflow: hidden; /* これが無いと縮まない */
    padding-block: 2rem;
    padding-inline: 2rem;

    @media (prefers-reduced-motion: no-preference) { /* アニメーションが OFF の環境でなければ設定 */
      transition-property: border-style, padding, visibility;
      transition-duration: 0s, var(--transition-time), 0s; /* padding のみアニメーションさせる */
      transition-behavior: allow-discrete;
    }
  }
  &:not([open]) {
    grid: auto 0fr / 1fr; /* summary要素 の高さは auto、::details-content擬似要素 の高さは 0fr */

    &::details-content {
      border-style: none;
      padding-block: 0;
      visibility: hidden; /* content-visibility: visible; による表示を無効化 */

      @media (prefers-reduced-motion: no-preference) {
        transition-delay: var(--transition-time), 0s, var(--transition-time); /* 閉じ切るまで border と visibility を設定しない */
      }
    }
  }
}
</style>
…
<details>
  <summary> … </summary>
  <p> … </p>
  <p> … </p>
</details>

今回は、ボーダーのみのシンプルなデザイン例でした。背景色を付けたり、summary要素のマーカーを変更したりと、色々できると思うので、応用してみてください。

CodePen

See the Pen Animation of details element by nov (@numerofive) on CodePen.

::○○-content系擬似要素 が増えれば、div要素 の過度なネストも減らせますね。::before::marker のような擬似要素のネストもできるようになってきているので、新たなテクニックがどんどん登場しそうですね。

関連記事