CSSのみでdetails要素のアニメーションを実装する方法

カテゴリ:
CSS
投稿日:

広告

約1年前、JavaScriptを使用したdetails要素のアニメーションの実装方法についての記事を投稿しました。しかし、その後の CSS の進化により <details> 要素の開閉アニメーションが CSS のみで実装可能になりました。

正確には、開く際のアニメーションは以前から CSS で実現できましたが、閉じる際のアニメーションは open 属性が即座に削除されるという仕様上、 JavaScript なしでは不可能でした。

この度、 details-content 疑似要素がサポートされたことにより、 CSS のみで閉じる際のアニメーションも実現できるようになりました。

::details-content - CSS: Cascading Style Sheets | MDN

The ::details-content CSS pseudo-element represents the expandable/collapsible contents of a <details> element.

developer.mozilla.org

details-content 疑似要素

details-content は、 <details> 要素のコンテンツスロットをラップするコンテナとなる疑似要素です。

従来、 <details> 要素のコンテンツ部分に padding などを設定する際には、コンテンツ全体を <div> などでラップする必要がありました。

従来のHTML
<details>
<summary>ラベル</summary>
<div>
<p>コンテンツ</p>
<p>コンテンツ</p>
<p>コンテンツ</p>
</div>
</details>
従来のCSS
summary + * {
padding: 1em;
background-color: #ddd;
}

details-content疑似要素の登場により、このラップ用の <div> が不要になります。

これからのHTML
<details>
<summary>ラベル</summary>
<p>コンテンツ</p>
<p>コンテンツ</p>
<p>コンテンツ</p>
</details>
これからのCSS
details {
&::details-content {
padding: 1em;
background-color: #ddd;
}
}

これだけ見ると、 <div> が1つ減る程度のメリットしかなく、大きな利点とは感じにくいかもしれません。details-content 疑似要素の真価は、この疑似要素を対象にアニメーションを設定することで、閉じる際のアニメーションも CSS のみで実現できる点にあります。

アニメーションの設定例
details {
&::details-content {
@media (prefers-reduced-motion: no-preference) {
transition-duration: 300ms;
transition-property: content-visibility, opacity;
transition-behavior: allow-discrete;
}
}
&:not([open])::details-content {
opacity: 0;
}
}

details-content 疑似要素に対して、 UA スタイルシートで指定されている content-visibility と、アニメーションさせたいプロパティ(この例では opacity )を transition-property に指定します。 content-visibility は離散プロパティであるため、その遷移を許可するために transition-behavior: allow-discrete も同時に指定する必要があります。

上記の例では、開閉時にフェードイン/アウトのアニメーションが適用されます。

アニメーションに敏感な方へ配慮し、 prefers-reduced-motion: no-preference メディア特性を用いて、視覚効果(アニメーション)を減らす設定がされていない場合にのみアニメーションを実行するようにすることも忘れないようにしましょう。

details-content 疑似要素は、 Chrome と Edge ではバージョン 131 から、 Safari ではバージョン 18.4 からサポートされています。

Firefox は記事執筆時点(2025年5月)では未サポートですが、Interop 2025の重点分野に選出されているため、今年中にはサポートされる見込みです。

コンテンツ全体のラッパーとして paddingbackground-color などのスタイルを指定する目的で details-content 疑似要素を使用するのは、ブラウザのサポート状況を考えるとまだ時期尚早かもしれません。しかし、アニメーションのみを目的とするならば、 details-content 疑似要素がサポートされていない環境ではアニメーションが発生しないだけで、 <details> 要素の基本的な動作に悪影響を与えるわけではないため、プログレッシブ・エンハンスメントとして許容しても良いと思います。

slideToggle 的なアニメーションを実装する

<details> 要素でよく用いられるアニメーションといえば、 jQuery の slideToggle() のような高さが変化する動作でしょう。

今までは CSS のみで要素の高さを 0 から auto(またはその逆)へとアニメーションさせることはできませんでした。そのため、 JavaScript を利用するか、 max-height に十分大きな固定値を指定するといった、やや限定的な実装方法しかありませんでした。

しかし、auto , fit-content , max-content といったキーワードが示す実際の数値を計算に利用できる calc-size() 関数や、これらのキーワードによるアニメーションを許可するかどうかを指定する interpolate-size: allow-keywords が登場し、 CSS のみでも 0 から auto への高さアニメーションが可能になりました。

interpolate-size: allow-keywords を利用した実装例

See the Pen by tak-dcxi (@tak-dcxi) on CodePen.

interpolate-size: allow-keywords を利用したアニメーションの設定例
:root {
interpolate-size: allow-keywords; /* サイズキーワードによるアニメーションを許可 */
}
details {
&::details-content {
@media (prefers-reduced-motion: no-preference) {
overflow: clip;
transition-duration: 300ms;
transition-property: content-visibility, block-size;
transition-behavior: allow-discrete;
}
}
&:not(:open)::details-content {
block-size: 0;
}
}

上の例では、まず :rootinterpolate-size: allow-keywords を指定して、ドキュメント全体でサイズキーワードによるアニメーションを許可しています。そして、 details-content 疑似要素の block-size に対して transition を設定しています。

:open<details> , <select> , <dialog> , <input type="datetime-local"> がオープン状態の時に適用される疑似クラスです。現状では Chrome と Edge でのみサポートされており、基本的には [open] 属性セレクタで代替できるため、現時点で使用する必要性は低いかもしれませんが、参考として紹介します。

calc-size()interpolate-size: allow-keywords は、少ないコストで簡単に slideToggle() のような動作を実現できる便利な CSS 機能ですが、記事執筆時点では Chrome と Edge のみのサポートとなっています。

details-content 疑似要素をサポートしていない Firefox はともかく、 details-content 疑似要素をサポートした Safari でこれらの機能が利用できないのは残念な点です。今後のアップデートに期待しましょう。

Safari でも details-content 疑似要素で slideToggle を実現する

calc-size()interpolate-size: allow-keywords がサポートされていないからといって、 Safari で details-content 疑似要素の slideToggle 的なアニメーションが実現できないわけではありません。

一部の Web メディアでは、 details-content 疑似要素の slideToggle が未対応であるため、固定数値へのアニメーションをフォールバックとして紹介しているケースもありますが、そのような手段を取らずとも Safari で slideToggle を実現する方法は存在します。

それは、以前の記事でも紹介した、 grid-template-rowstransition を利用する方法です。

grid-template-rows を利用した実装例

See the Pen by tak-dcxi (@tak-dcxi) on CodePen.

overflow:hidden用のDOMを追加
<details>
<summary>ラベル</summary>
<!-- grid-template-rowsアニメーションのため -->
<div style="overflow: hidden;">
<div class="_ContentWrapper">
<p>コンテンツ</p>
<p>コンテンツ</p>
<p>コンテンツ</p>
</div>
</div>
</details>
grid-template-rows を利用したアニメーションの設定例
details {
&::details-content {
content-visibility: unset;
display: block grid;
@media (prefers-reduced-motion: no-preference) {
transition-duration: 300ms;
transition-property: grid-template-rows;
}
}
&:not([open])::details-content {
grid-template-rows: 0fr;
}
&[open]::details-content {
grid-template-rows: 1fr;
}
}
._ContentWrapper {
padding: 1em;
background-color: #ddd;
}

この方法では、 details-content 疑似要素に display: block grid を設定し、開閉時に grid-template-rows の値を 0fr1fr の間で切り替えます。

grid-template-rows をアニメーションさせる場合、コンテンツのスタイルを適用するラッパーとは別に、 overflow: hidden を指定したラッパー要素がもう一つ必要になります。この際、スクロールコンテナを生成しないと grid-template-rows はアニメーションされないため、 overflow: clip ではなく overflow: hidden を指定してください。

現在のSafariには、 details-content 疑似要素の content-visibility プロパティの transition が意図した通りに動作しないという、あまり知られていない現象が存在します。具体的には、 transition 自体は発生するものの、コンテンツの高さがアニメーションの開始とほぼ同時に消失してしまうというものです。

これを回避するために、 UA スタイルシートで定義されている content-visibilityunset でリセットします。私がテストした範囲では、 content-visibility をリセットしても、主要なブラウザで <details> 要素の動作に支障はなく、ページ内検索時に内包されたコンテンツがヒットした場合の自動展開も問題なく動作しました。content-visibilityunset するレンダリングコストは気がかりですが、大きな問題は発生しない可能性が高いと考えられます。

なお、content-visibility をリセットした結果、 transition-behavior: allow-discrete の指定は不要になります。

CSS のみで実装するメリット

CSS のみで実装するメリットとしては次のとおりです。

  • JavaScript を使用しない分、パフォーマンスが良い。
  • JavaScript を用いた実装例には閉じたあとにコンテンツ部分を height の値を 0 のままにする、もしくは display: none するというアンチパターンが存在するが、それを防止しやすい。

JavaScript を用いた例は上記で挙げたアンチパターンが行われがちです。<details> 要素には(hidden="until-found" がサポートされている環境では)コンテンツがページ内検索でヒットした際に自動的に展開されるという利点があります。JavaScript で操作する場合は閉じたあとにページ内検索でヒットしても height0 のままだと自動展開されず、display: none するとページ内検索でヒットすらしません。故にこの便利機能を潰すことになりがちです。

一方、 CSS のみの場合は open 属性の有無で開閉されるので、この問題点を回避できます。

grid-template-rows を使う例は余分な <div> を増やしたり、ややハッキーな方法を用いる必要があり、基本的には interpolate-size: allow-keywords を利用したほうが良いでしょう。ただしこちらは Safari や Firefox でのサポートの見込みが立っていないこともあり、より多くの環境でアニメーションさせたいなら現状は grid-template-rows のほうが優勢です。

slideToggle のようなアニメーションを実装するために、都度 JavaScript を記述する手間を考えると、未サポートの環境についてはプログレッシブ・エンハンスメントと割り切って CSS オンリーの実装を選択するのも有効な手段だと考えています。

折りたたみメニュー