【2023年最新版】HTMLのアコーディオンはdetailsやsummaryを使おう!

サトケン
Webサイトでアコーディオン(トグル)を実装する時はどうしている?

ハメカメ師匠。
そうですね。。
昔はjQueryのslideToggle()を使ったり・・・
最近はslideToggle()を使わずに自作でアコーディオンを構築したりしていますが・・・。
むははは。 いいじゃろう。
脱jQueryは意識しておるのだな・・・
それでも問題なく動作はするであろうが
<details>や<summary>は使用しているか?

<details>!? <summary>!?
なんですか? そのタグは?
フフフ・・
20万WEBパワーの弱小WEBコーダーじゃのう・・・・

がーん・・・・!!
20万WEBパワー!! 弱小WEBコーダー!?
フフフ・・
よかろう
お前に今から
48の必殺WEBの一つ
「汎用性アコーディオン」を伝授しよう!
そもそもアコーディオンとは?
HTMLのアコーディオン(トグル)とは、ウェブページ上で情報をコンパクトに表示するための便利な機能です。 アコーディオンはタイトル(ボタン)と、コンテンツで構成されタイトル(ボタン)をクリックすることによりコンテンツ部分が表示・非表示に切り替わることからアコーディオン(トグル)と表現されます。 HTMLでマークアップしてCSSでデザインをカスタマイズしJavaScriptで動作させます。
chotGPT書房刊
「あぁ青春の我がHTMLアコーディオン」より
<details>と<summary>について
IE11が2022年6月15日にサポートが終了し 2023年2月14日(日本時間2月15日)以降は使用できなくなったことにより今まで使用を控えていたタグである<details>と<summary>が使えるようになり新しいアコーディオンの選択肢となりました。
HTML
コピー
<details>
<summary>アコーディオンのタイトルが入ります。</summary>
アコーディオンのコンテンツのテキストになります。<br>
アコーディオンのコンテンツのテキストになります。
</details>
<details>
を使用したシンプルなアコーディオン
アコーディオンのタイトルが入ります。(クリックすると開きます。)
アコーディオンのコンテンツのテキストになります。アコーディオンのコンテンツのテキストになります。
最低限のアコーディオンの開く・閉じるの動作をCSSハックやJavascriptの記述なしで実現できます。
ただメリット・デメリットもあります。
メリット
以下メリットになります。
- ・コードを見ただけで、極度の鈍感・近眼な方を除いてひと目見ただけでアコーディオンする箇所と認識できる。
-
・アクセシビリティ面・ユーザビリティ面に優れている
タブフォーカスとエンターキー・スペースキーでの開閉操作がデフォルトで狩野英孝状態。
デメリット
-
・連打対策が必要(JSで対応)
<details>
タグのopen属性の機能で開く・閉じるの動きを素早くするとアニメーション動作が一瞬チカる。 -
・Safari対策が必要(CSSで対応)
<summary>タグでbefore・afterといったCSS疑似要素をtransform: rotate()で回転させようとすると回転しないバグあり。
@keyframesで作成したアニメーションで回避もできますが作りがややこしくなるのでHTML上無駄なタグが増えますが今回は<span>タグで回避。
IE11対策が必要なくなっても今後はSafariに一番悩まされそうな気配・・・ -
・アコーディオンが開く際、必ず<summary>(タイトル)の下に開くという仕様がある。
上部にテキストがあり下部に「もっと見る」「MORE」ボタンがありクリックすることにより隠れている下部のテキストを表示させるということが仕様上難しい。(CSSで余白を下部につくり<summary>をposition:absolute;で強引に下部に配置することは可能だが動きが微妙になってしまいます。 要検討かつ要健闘。)
汎用性アコーディオンとは
上記のような作成時にデメリットもありますがそのデメリットにしっかり対応したものを一度キチンと作成してCSSとJavascriptを変更することなく、<div><a><button>を<details>><summary>に差し替えても問題なく動作するアコーディオンのことをいいます。
(※勝手に命名しているだけです・・・)
- ※なんと今回先着100,000名様にテンプレートとなるファイルはダウンロード可能です。
- ※サンプルのソースコードは自由にお使いいただけますが、自己責任においてご使用ください。
- ※
hoge-item
の箇所をお好みの名前に適宜変更してお使いください。
HTMLの記述
通常版
<div>と<a>・<button>でマークアップ
HTML
コピー
<div class="hoge-items">
<div class="hoge-item js-toggle-item">
<a href="#" class="toggle-btn">divタイプのアコーディオンのタイトルが入ります。<span class="toggle-icon"></a>
<div class="hoge-item__con toggle-con">
<div class="hoge-item__inner">
<p class="hoge-item__txt">divタイプアコーディオンのコンテンツのテキストになります。</p>
<p class="hoge-item__txt">divタイプとdetailsタイプと同じ動きになりますね。HTMLのコードもほぼ同じです。</p>
</div>
</div>
</div>
</div>
<details>と<summary>でマークアップ
HTML
コピー
<div class="hoge-items">
<details class="hoge-item js-toggle-item">
<summary class="toggle-btn">detailsタイプのアコーディオンのタイトルが入ります。<span class="toggle-icon"><span></span><span></span></span></summary>
<div class="hoge-item__con toggle-con">
<div class="hoge-item__inner">
<p class="hoge-item__txt">detailsタイプアコーディオンのコンテンツのテキストになります。</p>
<p class="hoge-item__txt">divタイプとdetailsタイプと同じ動きになりますね。HTMLのコードもほぼ同じです。</p>
</div>
</div>
</details>
</div>
2,3,10行目のHTMLのタグを入れ替え
<summary>の場合はSafari対策として<span class="toggle-icon"><span></span><span></span></span>に変更します。
それだけで汎用性アコーディオンは同じ挙動となります。
要するにアコーディオンの要素(ボタンとコンテンツ)をjs-toggle-itemで囲みその下の階層に同列でボタンtoggle-btnとコンテンツtoggle-conを配置します。
─ js-toggle-item
── toggle-btn
── toggle-con
決め事はjs-toggle-item要素の中にtoggle-btnとtoggle-conを配置するだけ。
HTMLタグは<div>でも<summary>どちらでも問題ございません。
なぜ「js-toggle-item」だけ頭に「js-」をつけているのかというとこの「js-toggle-item」をもとにJavascript側で操作しているからです。 jsでこのタグを使用しているのをhtml上でも分かりやすくするための「js-」になります。 接頭語と呼ばれています。
最初から開いた状態
<div>と<a>・<button>でマークアップ
HTML
コピー
<div class="hoge-items">
<div class="hoge-item js-toggle-item state-active">
<a href="#" class="toggle-btn">divタイプのアコーディオンのタイトルが入ります。<span class="toggle-icon"></a>
<div class="hoge-item__con toggle-con">
<div class="hoge-item__inner">
<p class="hoge-item__txt">divタイプアコーディオンのコンテンツのテキストになります。</p>
<p class="hoge-item__txt">divタイプとdetailsタイプと同じ動きになりますね。HTMLのコードもほぼ同じです。</p>
</div>
</div>
</div>
</div>
js-toggle-item
にstate-active
クラスを付加する
- ※独自でJSで作成している機能になります。
<details>と<summary>でマークアップ
HTML
コピー
<div class="hoge-items">
<details class="hoge-item js-toggle-item" open>
<summary class="toggle-btn">detailsタイプのアコーディオンのタイトルが入ります。<span class="toggle-icon"><span></span><span></span></span></summary>
<div class="hoge-item__con toggle-con">
<div class="hoge-item__inner">
<p class="hoge-item__txt">detailsタイプアコーディオンのコンテンツのテキストになります。</p>
<p class="hoge-item__txt">divタイプとdetailsタイプと同じ動きになりますね。HTMLのコードもほぼ同じです。</p>
</div>
</div>
</details>
</div>
<details>
にopen
属性を付加する
- ※
<details>
のデフォルトの機能になります。
CSSの記述
三角形アイコンを非表示に・カーソルを指マークに変更
デフォルトCSS対策とSafari対策が必要になります。
CSS
コピー
summary {
/* display: list-item;以外を指定してデフォルトの三角形アイコンを消す しかしSafariにはききません。。 */
display: block;
/* デフォルトで指マークになっていないのでcursorをpointerに変更 */
cursor: pointer;
}
summary::-webkit-details-marker {
/* 不屈の闘志でSafariで表示されるデフォルトの三角形アイコンを消します */
display: none;
}
summary{
cursor: pointer;
}
三角形アイコンが非表示になり、指マークに変更された<details>と<summary>になります。
下記が変更後のアコーディオンになります。
アコーディオンのタイトルが入ります。(クリックすると開きます。)
アコーディオンのデフォルトのにっくき三角形アイコンが消えてしまいました。アコーディオンのデフォルトのにっくき三角形アイコンが消えてしまいました。
コンテンツ部分を非表示に
CSS
コピー
.toggle-con {
/* アコーディオンで表示・非表示を切り替えられるコンテンツ部分を非表示の状態にする */
overflow: hidden;
height: 0;
}
アニメーションさせるアコーディオン作成時に<details>と<summary>を使用する・しない関係なしにコンテンツ部分を非表示にする必要があります。
- ・三角形アイコンを非表示に・カーソルを指マークに変更
- ・コンテンツ部分を非表示に
上記の2点がアコーディオン作成時に必ず必要になるCSSであとはアコーディオンに対するデザイン調整のCSSになります。
ただし注意点が一点だけあります。
コンテンツ箇所の余白を取る時、<toggle-con>(コンテンツ箇所の一番上の階層)で余白をとるのではなく
<toggle-item__inner>(その一つ下の階層)で余白をとるようにしてください。
HTML
コピー
<div class="hoge-items">
<details class="hoge-item js-toggle-item">
<summary class="toggle-btn">アコーディオンのタイトルが入ります。<i class="toggle-icon"></i></summary>
<div class="hoge-item__con toggle-con"> ←こちらではなく
<div class="hoge-item__inner"> ←こちらで余白を取る
<p class="hoge-item__txt">アコーディオンのコンテンツのテキストになります。</p>
<p class="hoge-item__txt">アコーディオンのコンテンツのテキストになります。アコーディオンのコンテンツのテキストになります。</p>
</div>
</div>
</details>
</div>
余白をとる箇所を間違えてしまうとアコーディオンアニメーション時に挙動がガクつく感じになってしまいます。 この一点だけ気をつけてあとは自分のお好みのデザインのアコーディオンを作成してください。
アコーディオン箇所のサンプルCSS
参考までに下記がKEPRATEサイトのFAQで使用しているのデザインのアコーディオンとなります。
CSS
コピー
summary::-webkit-details-marker {
/* Safariで表示されるデフォルトの三角形アイコンを消します */
display: none;
}
summary {
/* カーソルは指マークに */
cursor: pointer;
}
.toggle-con {
/* アコーディオンで表示・非表示を切り替えられるコンテンツ部分を非表示の状態にする */
overflow: hidden;
height: 0;
}
.hoge-items {
width: 100%;
margin: 0 auto;
text-align: left;
}
.hoge-items .hoge-item {
border-bottom: 1px solid #ccc;
}
.hoge-items .hoge-item:last-child {
border-bottom: none;
}
/* safari */
.hoge-item.state-active .toggle-btn .toggle-icon span:first-child, .hoge-item.state-active .toggle-btn .toggle-icon:after {
transform: rotate(90deg);
}
.hoge-item .toggle-btn .toggle-icon span:first-child, .hoge-item .toggle-btn .toggle-icon:after {
transform: rotate(0deg);
}
.hoge-item summary.toggle-btn .toggle-icon::after, .hoge-item summary.toggle-btn .toggle-icon::before {
display: none;
}
.hoge-item .toggle-btn {
position: relative;
z-index: 1;
display: block;
padding: 15px 25px 15px 45px;
font-size: 14px;
font-weight: 500;
line-height: 1.8;
}
@media print, screen and (min-width: 768px) {
.hoge-item .toggle-btn {
padding: 44px 100px 44px 80px;
font-size: 20px;
}
}
.hoge-item .toggle-btn::after {
content: 'Q';
position: absolute;
display: block;
left: 15px;
font-weight: 500;
font-size: 22px;
z-index: 2;
color: #a61f24;
top: 50%;
transform: translateY(-50%);
}
@media print, screen and (min-width: 768px) {
.hoge-item .toggle-btn::after {
left: 26px;
font-size: 40px;
}
}
.hoge-item .toggle-btn .toggle-icon {
position: absolute;
top: 50%;
right: 2.66667vw;
width: 12px;
height: 12px;
margin-top: -6px;
}
@media print, screen and (min-width: 768px) {
.hoge-item .toggle-btn .toggle-icon {
width: 20px;
height: 20px;
margin-top: -10px;
right: 20px;
}
}
.hoge-item .toggle-btn .toggle-icon span:last-child, .hoge-item .toggle-btn .toggle-icon::before {
display: block;
position: absolute;
top: 50%;
margin-top: -1px;
left: 0;
width: 100%;
height: 2px;
background: #111;
content: "";
z-index: 1;
transition: transform 0.3s 0s ease;
}
.hoge-item .toggle-btn .toggle-icon span:first-child, .hoge-item .toggle-btn .toggle-icon::after {
display: block;
position: absolute;
top: 0;
margin-left: -1px;
left: 50%;
width: 2px;
height: 100%;
background: #111;
content: "";
z-index: 2;
transition: transform 0.3s 0s ease;
}
.hoge-item__inner {
padding: 20px 20px 20px 45px;
font-size: 14px;
font-weight: 400;
line-height: 2;
position: relative;
background: #f4f4f4;
border-radius: 16px;
overflow: hidden;
margin-bottom: 20px;
}
@media print, screen and (min-width: 768px) {
.hoge-item__inner {
padding: 40px 100px 40px 80px;
font-size: 16px;
border-radius: 16px;
margin-bottom: 40px;
}
}
.hoge-item__inner::after {
content: 'A';
position: absolute;
display: block;
left: 15px;
font-weight: 500;
font-size: 22px;
z-index: 2;
color: #333;
top: 12px;
}
@media print, screen and (min-width: 768px) {
.hoge-item__inner::after {
left: 26px;
font-size: 40px;
top: 16px;
}
}
.hoge-item__inner .hoge-item__txt {
font-size: 14px;
line-height: 1.8;
margin-bottom: 10px;
}
@media print, screen and (min-width: 768px) {
.hoge-item__inner .hoge-item__txt {
font-size: 18px;
margin-bottom: 12px;
}
}
.hoge-item__inner .hoge-item__txt:last-child {
margin-bottom: 0;
}
JSの記述
今回JSアニメーションライブラリはGSAPを使用しております。
<details>
はデフォルトの機能として<open>
属性の有無でコンテンツ部分を表示・非表示としているようです。 この機能のせいで表示・非表示が一瞬で行われてしまい、JSでアニメーションを実装する際に必要のない機能となります。
JSでは以下の作業がポイントとなります。
- ・
<open>
がデフォルトのタイミングで発動しないように<summary>
要素をクリック時にデフォルトの機能を無効化させるpreventDefault()
- ・open属性を任意のタイミングで追加・削除(アニメーション完了時)する
- ・
<open>
のデフォルトの機能のため、連打すると挙動がおかしくなるので対策が必要
デフォルトの機能を無効化
JS
コピー
const toggleClick = (e) => {
e.preventDefault(); //デフォルトの動作をキャンセル
}
toggleBtn.addEventListener("click", toggleClick);
クリック時にe.preventDefault();
でデフォルトの動作をキャンセルします。
アニメーション完了時にopen属性を削除
JS
コピー
gsap.to(toggleCon, {
height: toggleH,
ease: toggleEase,
duration: toggleT,
onComplete: () => {
//アコーディオンのアニメーション完了時
//高さが0の場合 = アコーディオンを閉じた場合
if (toggleH == 0) toggle.removeAttribute("open");
}
})
アコーディオンアニメーションが終了したタイミングでアコーディオンの高さが0の時はremoveAttribute("open");
を実行します。
連打対策
JS
コピー
//もしstate-animateクラスがあれば(アニメーションが終了していない状態)移行の処理は実行させない
if (toggle.classList.contains('state-animate')) {
return;
}
//アニメーションが始るタイミングでstate-animateクラスを付加
toggle.classList.add('state-animate');
gsap.to(toggleCon, {
height: toggleH,
ease: toggleEase,
duration: toggleT,
onComplete: () => {
//アニメーションが終了したタイミングでstate-animateクラスを削除
toggle.classList.remove('state-animate');
}
})
state-animate
クラスの有無でアニメーションが終了していない場合は移行の処理を実行させないようにする
以下JS全ソースになります。
─ js-toggle-item
── toggle-btn
── toggle-con
JS内で使用されている要素は上記だけでありHTML内のその他の要素の影響がない作りになっているのが確認できるかと思われます。 あとはCSSでサイトに適したデザイン調整作業に集中することが可能です。
JS
コピー
const toggleFunction = () => {
const toggleItems = document.querySelectorAll(".js-toggle-item");
const toggleItemsLen = toggleItems.length;
const breakPoint = 768; //任意で変更してください
for (let i = 0; i < toggleItemsLen; i++) {
const toggle = toggleItems[i];
const toggleBtn = toggle.getElementsByClassName('toggle-btn')[0];
const toggleCon = toggle.getElementsByClassName('toggle-con')[0];
if(toggle.open) toggle.classList.add('state-active');
if (toggle.classList.contains('state-active')) {
toggleCon.style.height = 'auto';
} else {
toggleCon.style.height = 0;
}
const toggleClick = (e) => {
e.preventDefault(); //デフォルトの動作をキャンセル
if (toggle.classList.contains('state-animate')) {
return;
}
let toggleH;
//閉じる・開く時の動作を振り分け変更したい時は変更してください
let toggleT = 0.4;
let toggleEase = "power4.out";
if (!toggle.classList.contains('state-active')) {
//アコーディオンが閉じているとき
toggle.classList.add('state-active');
toggleH = "auto";
toggle.setAttribute("open", "true");
} else {
//アコーディオンが開いているとき
toggle.classList.remove('state-active');
toggleH = 0;
}
toggle.classList.add('state-animate');
gsap.to(toggleCon, {
height: toggleH,
ease: toggleEase,
duration: toggleT,
onComplete: () => {
//アコーディオンのアニメーション完了時
if (toggleH == 0) toggle.removeAttribute("open");
toggle.classList.remove('state-animate');
}
})
}
toggleBtn.addEventListener("click", toggleClick);
}
}
document.addEventListener('DOMContentLoaded', function() {
toggleFunction();
});
まとめ
デメリットもありますが動作的には上記のコードを流用すると問題なくなります。
今後は<details>
と<summary>
タグを使用するのがアクセシビリティ対策も簡単にでき、今後はメジャーになってくることが予想されますのでこの機会にまだ使用されていない方は是非一度お試しください。
どうじゃ・・・
<details>
と<summary>
はこれから激アツじゃろう・・

・・・
・・・
・・・
zzz。。。
・・・
説明が長いっす。。。
・・・す すまん・・・