アコーディオンの実装について、アクセシビリティを配慮した時の実装方法はどうすべきか、改めて考えてみました。
(2022年10月)
今回目標とする実装について先にまとめておくと、
- アクセシビリティに配慮して実装したい。
- Tabキーで選択でき、エンター・スペースキーの両方で開閉可能である。
- スクリーンリーダーでの読み上げもおかしくならないようにする。
- 展開アニメーションを付けたい。
- アイコンも独自のものを使用し、アニメーションさせたい。
ってな感じです。
アコーディオンの開閉アニメーションの実装方法について
height: autoを使うと、heightのtransitionアニメーションが動作しないという問題があるため多くの場合はJSで開閉アニメーションを実装すると思うのですが、個人的には以下の記事で紹介しているごまかしアニメーションで十分だと思っています。
なので、このCSSにアニメーションを任せる方法を取れるかどうか、というのが比較する際の重要ポイントになっています。
考えられる2つの実装パターン
IEを無視できるようになった今、details / summary での実装も実用的になってきました。
なので、アコーディオンの実装方法については、現在だと主に2つの選択肢があると思います。
<details>
と<summary>
タグを使うパターン- WAI-ARIA対応を自力で行うパターン
この2つの実装パターンにはどう違いがあるのか?どっちの方法で実装するのがいいのだろうか...?
ということについて、今回はじっくり考えてみました。
また②のWAI-ARIA対応を自力で行うパターンについてですが、これは具体的にはさらに何パターンかの実装方法があります(ググるとでてきます)よね。
そこに関しては、個人的には以下の実装パターンが一番いいかなと思っています。
<button>
タグを使って、aria-expanded
とaria-controls
でWAI-ARIA対応させる方式
今回比較していくWAI-ARIAでの実装については、この方式で進めていきます。
W3CのWAI-ARIA対応アコーディオンのデモもこの形式ですね。(見出しタグについては少し気になりますが...)
他にメジャーな方法としてチェックボックスを使う方法もありますが、個人的にはこれはいつも避けています。
(標準でエンターキーでの操作ができない・個人的にDOM構造が気持ち悪い)
要検証メモ:アコーディオンのラッパー要素のタグを<section>
などにすれば、aria-expanded
は<button>
側ではなくラッパー側に移してaria-controls
を使わなくても大丈夫かも...?
(ただしセクションなのでアコーディオンタイトル部分はhタグにする必要もでてきそう...)
参考:https://masup.net/2018/10/common-mistake-when-use-aria-expanded
それぞれ実際に実装してみる
※ コードの具体的な解説をしちゃうと超長くなるので、この記事では割愛します。(気が向いたら別記事にまとめてみます)
details / summary での実装例
実装例A:まずは一番シンプルな実装をしてみます。
See the Pen details/summaryでのアコーディオン実装 (プレーンな状態) by ddryo (@ddryo-the-encoder) on CodePen.
機能的にこれで十分なケースであれば、あとはCSSで体裁整えるだけでいいのでめちゃくちゃ楽ですね。
実装例B:次に、アイコン部分だけアニメーションさせるパターンで実装してみました。
See the Pen Untitled by ddryo (@ddryo-the-encoder) on CodePen.
正直、ほとんどの場合これくらいで丁度良いというか、機能的に十分な気がしますね。
実装例C:JSで開閉アニメーションを付けるパターンで実装してみました。
See the Pen details/summaryでのアコーディオン実装 (展開アニメーション付き) by ddryo (@ddryo-the-encoder) on CodePen.
これが、想像以上に面倒でした...!
details / summary でのアニメーションに関しての基本的なことについては、以下の ICSメディアさんの記事が非常に参考になりました。
ただ、この方法だと .accordion__content
にpaddingを付けれないというのが個人的に(すでに実装済みのGutenbergブロックの後方互換の問題的に)アウトでした。
なので、今回の例では次のような方法を取っています。
<summary>
の高さをアニメーションさせるのではなく、<details>
の高さをアニメーションさせる。
そうすることで、上記のpadding問題は解決できたっぽいです。
(その代わり、少しだけJSが複雑になってしまいますが...。)
ちなみにこのdetails側のheightをいじるという発想は、以下のCodePenからいただきました。
アニメーションはJSで書かなきゃだめなの?
って話なんですが、後述するページ内テキスト検索の挙動も考慮すると、JS側でアニメーションさせなきゃけっこうキツかったです。
ページ内テキスト検索時にコンテンツが表示されると、クリックイベントが発火せずに open 属性が付与されちゃうんですよね。
なので、is-opendクラスの有無とopenの有無に矛盾が生じてしまい、CSSでのごまかしアニメーションが使えませんでした。
ここの挙動にも不備がないようにするためにはJSでやるしか僕はできませんでした。
→ CSSにアニメーション任せられるいい方法、ありました!
実装例D:追加で、CSSで開閉アニメーションを付ける方法を思いついたので実装してみました。
<details>
のopenが切り替わる時、'toggle'
イベントというものが発火されるので、そちらを活用することで問題を解消することができました!!
See the Pen アコーディオン実装D (details/summary, CSSで開閉アニメーション) by ddryo (@ddryo-the-encoder) on CodePen.
上記はごまかしアニメーションで実装してます。
そうじゃなくてちゃんと開閉の動きを見せつつJSのコード量をある程度抑えたい場合は、以下のような実装もありかと思います!
See the Pen details/summaryの開閉アニメーション実装 - A by ddryo (@ddryo-the-encoder) on CodePen.
WAI-ARIA での実装例
実装例1:CSS側でできるごまかしアニメーションのパターン
See the Pen WAI-ARIAでアクセシビリティに配慮したアコーディオンの実装例 by ddryo (@ddryo-the-encoder) on CodePen.
実装例2:簡易ごまかしアニメーションではなくしっかりさせたパターン
See the Pen アコーディオン実装 (WAI-ARIA, CSS変数を使った開閉アニメーション付き) by ddryo (@ddryo-the-encoder) on CodePen.
実装例3:アニメーションを全部JSで任せるパターン
See the Pen アコーディオン実装 (WAI-ARIA,JSでアニメーション, jQueryなし) by ddryo (@ddryo-the-encoder) on CodePen.
各実装方法のメリット・デメリットについて
実際に両方やってみてわかったメリット・デメリットをまとめておきます。
「details/summary での実装」 のメリット・デメリット
- WAI-ARIAについて考えなくていい。
- 開閉アニメーションが不要であればHTMLだけで完結できる。
- ページ内テキスト検索時に、コンテンツが隠れていても自動で展開してくれる。
(ヘッダーナビゲーションなどにdetails/summary使うとそれが邪魔になるという声もあるみたいですが...。) - Gutenbergブロックとして実装する時も楽。
- 細かい問題点がいくつかあり、アコーディオンをdetails/summaryタグで実装することの是非ついて色々議論されてるっぽい。
特にsummary内でのhタグが読み上げ時に見出しレベルとして認識されない問題が大きい(このツイートのリプ欄での会話に貴重な情報が詰まってました)。
- アイコンについては、特にこだわりがなければ標準で三角アイコンが付いてくるからかなり楽だけど、逆に自分で用意する時はいちいち消さないといけなくて面倒。
- VoiceOver読み上げ時にその「三角形の選択ボタン」の読み上げがいちいち入るのが邪魔な気がする(読み上げ対象から除外する方法がわからなかった)
開閉アニメーションについては、「実装例D」のようにCSSのごまかしアニメーションでもいけました!
「WAI-ARIA での実装」のメリット・デメリット
- DOM構造に融通が効く(Hタグ > button にしたり、dlで実装したり。)
- 展開アニメーションが不要の場合でも、(量は少ないが)JSが必要。
- ページ内テキスト検索時、隠れたコンテンツ内に検索対象テキストがあっても検索ができない、もしくは、ヒットはするけどどこにあるかわからない状態になる。(visibilityの指定の有無によって挙動が変わるがどちらにせよ正常な検索はできない)
- Gutenbergブロックで作る場合、idの生成とかもしないといけないので少しだけ面倒。
- WAI-ARIAの書き方は本当にこれでいいのかと、どれだけ調べても不安が残る。(よほど普段からアクセシビリティなどについて勉強していて自信がある人でない限り...)
- 間違った書き方で解説しているブログ記事も多いので注意が必要。(このブログもそうかもしれないので自分で調べてやってみてね)
ページ内テキスト検索時の挙動について
これは見ていただくのが一番早いです。
details/summaryでの実装
WAI-ARIAでの実装(visibility:hidden)
hidden="until-found"
と'beforematch'
イベントを使うことで、details/summaryと同じように折りたたまれたコンテンツの中も検索できるようになるみたいです!
(参考:CodePen:Searchable Disclosure, until-foundについて)
ただし、ブラウザサポートの問題があるため、まだ実用的ではないかもしれません。(どこまでサポートされているか調べきれませんでしたが、2022/10/18現在、Chromeでは動き、MacのSafariでは動きませんでした)
結論:どちらの実装方法が良いか。
正直好きな方で良いと思います。笑
- 展開時の開閉アニメーションが不要なのであれば、details/summaryが圧倒的に簡単。
- WAI-ARIAについて自信がなければ details/summary が安心。
- ページ内テキスト検索を重要視するならdetails/summary。(将来的にはどちらでもよくなりそう)
って感じでしょうか。
個人的にはテキスト検索の挙動の違いから、details/summary を選ぼうと思いました!