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

広告
約1年前、JavaScriptを使用したdetails要素のアニメーションの実装方法についての記事を投稿しました。しかし、その後の CSS の進化により <details>
要素の開閉アニメーションが CSS のみで実装可能になりました。
正確には、開く際のアニメーションは以前から CSS で実現できましたが、閉じる際のアニメーションは open
属性が即座に削除されるという仕様上、 JavaScript なしでは不可能でした。
この度、 details-content
疑似要素がサポートされたことにより、 CSS のみで閉じる際のアニメーションも実現できるようになりました。
details-content 疑似要素
details-content
は、 <details>
要素のコンテンツスロットをラップするコンテナとなる疑似要素です。
従来、 <details>
要素のコンテンツ部分に padding
などを設定する際には、コンテンツ全体を <div>
などでラップする必要がありました。
<details> <summary>ラベル</summary> <div> <p>コンテンツ</p> <p>コンテンツ</p> <p>コンテンツ</p> </div></details>
summary + * { padding: 1em; background-color: #ddd;}
details-content
疑似要素の登場により、このラップ用の <div>
が不要になります。
<details> <summary>ラベル</summary> <p>コンテンツ</p> <p>コンテンツ</p> <p>コンテンツ</p></details>
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の重点分野に選出されているため、今年中にはサポートされる見込みです。
コンテンツ全体のラッパーとして padding
や background-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
への高さアニメーションが可能になりました。
: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; }}
上の例では、まず :root
に interpolate-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-rows
の transition
を利用する方法です。
<details> <summary>ラベル</summary> <!-- grid-template-rowsアニメーションのため --> <div style="overflow: hidden;"> <div class="_ContentWrapper"> <p>コンテンツ</p> <p>コンテンツ</p> <p>コンテンツ</p> </div> </div></details>
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
の値を 0fr
と 1fr
の間で切り替えます。
grid-template-rows
をアニメーションさせる場合、コンテンツのスタイルを適用するラッパーとは別に、 overflow: hidden
を指定したラッパー要素がもう一つ必要になります。この際、スクロールコンテナを生成しないと grid-template-rows
はアニメーションされないため、 overflow: clip
ではなく overflow: hidden
を指定してください。
現在のSafariには、 details-content
疑似要素の content-visibility
プロパティの transition
が意図した通りに動作しないという、あまり知られていない現象が存在します。具体的には、 transition
自体は発生するものの、コンテンツの高さがアニメーションの開始とほぼ同時に消失してしまうというものです。
これを回避するために、 UA スタイルシートで定義されている content-visibility
を unset
でリセットします。私がテストした範囲では、 content-visibility
をリセットしても、主要なブラウザで <details>
要素の動作に支障はなく、ページ内検索時に内包されたコンテンツがヒットした場合の自動展開も問題なく動作しました。content-visibility
を unset
するレンダリングコストは気がかりですが、大きな問題は発生しない可能性が高いと考えられます。
なお、content-visibility
をリセットした結果、 transition-behavior: allow-discrete
の指定は不要になります。
CSS のみで実装するメリット
CSS のみで実装するメリットとしては次のとおりです。
- JavaScript を使用しない分、パフォーマンスが良い。
- JavaScript を用いた実装例には閉じたあとにコンテンツ部分を
height
の値を0
のままにする、もしくはdisplay: none
するというアンチパターンが存在するが、それを防止しやすい。
JavaScript を用いた例は上記で挙げたアンチパターンが行われがちです。<details>
要素には(hidden="until-found"
がサポートされている環境では)コンテンツがページ内検索でヒットした際に自動的に展開されるという利点があります。JavaScript で操作する場合は閉じたあとにページ内検索でヒットしても height
が 0
のままだと自動展開されず、display: none
するとページ内検索でヒットすらしません。故にこの便利機能を潰すことになりがちです。
一方、 CSS のみの場合は open
属性の有無で開閉されるので、この問題点を回避できます。
grid-template-rows
を使う例は余分な <div>
を増やしたり、ややハッキーな方法を用いる必要があり、基本的には interpolate-size: allow-keywords
を利用したほうが良いでしょう。ただしこちらは Safari や Firefox でのサポートの見込みが立っていないこともあり、より多くの環境でアニメーションさせたいなら現状は grid-template-rows
のほうが優勢です。
slideToggle
のようなアニメーションを実装するために、都度 JavaScript を記述する手間を考えると、未サポートの環境についてはプログレッシブ・エンハンスメントと割り切って CSS オンリーの実装を選択するのも有効な手段だと考えています。