Webサイト上では、アコーディオンメニューがよく使われていますよね。
コンテンツを折り畳むことができるため、
- ユーザーが欲しい情報をピンポイントで探せる
- ページが縦長になりすぎない
- 少ないスペースで多くの情報を収納できる
といったようなメリットがあります。
このアコーディオンメニューの多くはjQueryのスライド系メソッドで実装されていますね。
素のJavaScriptで実装したいと思い調べてみると、以下のような問題点があるものばかり紹介されてました。
- paddingを指定すると崩れる
- 文章が複数行になると、閉じる時に潰れたような見た目になる
- 開閉のアニメーションが不自然
これならばjQueryを使った方がましですが、アコーディオンの実装だけのためにjQueryを読み込みたくはないですよね…。
そこで今回の記事では、jQueryのslide系メソッドと全く同じ挙動のアコーディオンメニューを素のJavaScriptで実装する方法を紹介します!
DEMOや実装コードも紹介していますので、JavaScriptでアコーディオンを実装したい方はぜひ参考にしてみてください。
アコーディオンの3種類のDEMOと実装コード
今回は3つのアコーディオンを用意しました。
- 通常バージョン
- 最初の一つを開けておくバージョン
- 一度に一つだけ開くバージョン
次でそれぞれのDEMOと実装コードを紹介していきます!
コードをコピペするだけで動作するので、自由にお使いください。
DEMO1. 通常バージョン
まずは一番シンプルなアコーディオンから見ていきましょう。
要素をクリックしてみてください。
- DEMO1のアコーディオンタイトル
- ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
- DEMO1のアコーディオンタイトル
- ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
- DEMO1のアコーディオンタイトル
- ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
DEMO1の実装コード
\ クリックでタブを切り替えられます /
<dl class="accordion js-accordion">
<div class="accordion__item js-accordion-trigger">
<dt class="accordion__title">DEMO1のタイトル</dt>
<dd class="accordion__content">DEMO1のコンテンツ</dd>
</div>
<div class="accordion__item js-accordion-trigger">
<dt class="accordion__title">DEMO1のタイトル</dt>
<dd class="accordion__content">DEMO1のコンテンツ</dd>
</div>
<div class="accordion__item js-accordion-trigger">
<dt class="accordion__title">DEMO1のタイトル</dt>
<dd class="accordion__content">DEMO1のコンテンツ</dd>
</div>
</dl>
DEMO2. 最初の一つを開けておくバージョン
ユーザーに開閉可能であることを示すために、最初のひとつだけ最初から開いておくバージョンです。
- DEMO2のアコーディオンタイトル
- ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
- DEMO2のアコーディオンタイトル
- ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
- DEMO2のアコーディオンタイトル
- ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
DEMO2の実装コード
\ クリックでタブを切り替えられます /
<dl class="accordion js-accordion">
<div class="accordion__item js-accordion-trigger is-active">
<dt class="accordion__title">DEMO2のタイトル</dt>
<dd class="accordion__content is-open">DEMO2のコンテンツ</dd>
</div>
<div class="accordion__item js-accordion-trigger">
<dt class="accordion__title">DEMO2のタイトル</dt>
<dd class="accordion__content">DEMO2のコンテンツ</dd>
</div>
<div class="accordion__item js-accordion-trigger">
<dt class="accordion__title">DEMO2のタイトル</dt>
<dd class="accordion__content">DEMO2のコンテンツ</dd>
</div>
</dl>
DEMO3. 一度に一つだけ開くバージョン
一度にひとつだけ開くバージョンです。
二つ以上開こうとすると、最初のアコーディオンが閉じる仕組みになっています。
- DEMO3のアコーディオンタイトル
- ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
- DEMO3のアコーディオンタイトル
- ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
- DEMO3のアコーディオンタイトル
- ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
DEMO3の実装コード
\ クリックでタブを切り替えられます /
<dl class="accordion js-accordion">
<div class="accordion__item js-accordion-trigger">
<dt class="accordion__title">DEMO3のタイトル</dt>
<dd class="accordion__content">DEMO3のコンテンツ</dd>
</div>
<div class="accordion__item js-accordion-trigger">
<dt class="accordion__title">DEMO3のタイトル</dt>
<dd class="accordion__content">DEMO3のコンテンツ</dd>
</div>
<div class="accordion__item js-accordion-trigger">
<dt class="accordion__title">DEMO3のタイトル</dt>
<dd class="accordion__content">DEMO3のコンテンツ</dd>
</div>
</dl>
アコーディオンの実装コードをそれぞれ解説【HTML・CSS・JavaScript】
ここからは、実装コードの解説をしていきます。
上記の順で解説していきますね。
HTML
まずはHTMLからみていきましょう。
<dl class="accordion js-accordion">
<div class="accordion__item js-accordion-trigger">
<dt class="accordion__title">DEMOのタイトル</dt>
<dd class="accordion__content">DEMOのコンテンツ</dd>
</div>
<div class="accordion__item js-accordion-trigger">
<dt class="accordion__title">DEMOのタイトル</dt>
<dd class="accordion__content">DEMOのコンテンツ</dd>
</div>
<div class="accordion__item js-accordion-trigger">
<dt class="accordion__title">DEMOのタイトル</dt>
<dd class="accordion__content">DEMOのコンテンツ</dd>
</div>
</dl>
アコーディオンの実装には、説明リストタグdl
・dt
・dd
を使用します。
dl
タグでアコーディオン全体を囲い、JavaScriptで指定するためにjs-accordion
のクラスを付与します。
dl
タグの中には、dt
とdd
のセットを囲んだdiv
タグを配置します。
ちなみによく勘違いされますが、dl
の中にdiv
を配置するのはOKです。
(参考: 説明リスト要素 – MDN)
このdiv
をクリックした際にコンテンツを展開or閉じたいので、js-accordion-trigger
クラスを指定しておきます。
あとはdt
にタイトル、dd
に展開/収縮させるコンテンツを入れて完成です!
最初からアコーディオンを開けておきたい場合はクラスを追加
DEMO2で紹介したように、最初からアコーディオンを展開しておきたい場合は、以下の2つのクラスを追加します。
<dl class="accordion js-accordion">
<div class="accordion__item js-accordion-trigger is-active">
<dt class="accordion__title">DEMOのタイトル</dt>
<dd class="accordion__content is-open">DEMOのコンテンツ</dd>
</div>
<div class="accordion__item js-accordion-trigger">
<dt class="accordion__title">DEMOのタイトル</dt>
<dd class="accordion__content">DEMOのコンテンツ</dd>
</div>
<div class="accordion__item js-accordion-trigger">
<dt class="accordion__title">DEMOのタイトル</dt>
<dd class="accordion__content">DEMOのコンテンツ</dd>
</div>
</dl>
accordion__item
にis-active
を追加accordion__content
にis-open
を追加
これではじめから要素が展開された状態になりました。
DEMOでは最初の一つのみ展開していましたが、複数展開することも可能です。
CSS
続いてはCSSの解説です。
まずは全体のCSSを確認してみましょう。
/* 簡易リセットCSS */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* アコーディオン全体 */
.accordion {
max-width: 800px;
margin: 0 auto;
}
/* アコーディオン */
.accordion__item {
border: 1px solid #ccc;
margin-top: 10px;
cursor: pointer;
}
/* アコーディオンのタイトル */
.accordion__title {
position: relative;
padding: 15px 60px 15px 20px;
font-weight: bold;
cursor: pointer;
}
/* (+)アイコン */
.accordion__title::before,
.accordion__title::after {
content: "";
position: absolute;
right: 20px;
top: 0;
bottom: 0;
margin: auto 0;
background-color: #3abec1;
width: 20px;
height: 4px;
transition: all 0.3s;
}
.accordion__title::after {
transform: rotate(90deg);
}
/* アコーディオンのコンテンツ */
.accordion__content {
padding: 0 20px 15px 20px;
display: none;
cursor: pointer;
}
/* アコーディオンのコンテンツ展開時 */
.accordion__content.is-open {
display: block;
}
/* アコーディオン展開時の(-)アイコン */
.accordion__item.is-active .accordion__title::before {
transform: rotate(180deg);
}
.accordion__item.is-active .accordion__title::after {
transform: rotate(180deg);
opacity: 0;
}
CSSのほとんどは見た目の調整なので、アコーディオンの機能に関わる重要な部分のみ説明します。
/* アコーディオン収縮時 */
.accordion__content {
display: none;
}
/* アコーディオン展開時 */
.accordion__content.is-open {
display: block;
}
まずアコーディオンのコンテンツ部分にdisplay: none
を指定します。
これで初期状態ではコンテンツが隠れるようになりました。
クリックするとコンテンツを展開したいので、is-open
クラスがついている時にdisplay: block
を指定します。
このis-open
クラスはJavaScriptで付与/削除するので、ここでは考える必要はありません。
DEMOでは値にblock
を指定していますが、flex
やinline-block
等も指定することができます。
この部分以外は全て見た目の調整なので、自由にカスタマイズしてください。
これでCSSの説明は終わりです。
JavaScript
最後にアコーディオンの機能部分であるJavaScriptの解説をしていきます。
JavaScriptのコードは、大きく以下の2つに分かれます。
slideUp
/slideDown
/slideToggle
関数の定義- DOM操作
それぞれ別々に説明していきますね。
JSパート1. slideUp, slideDown, slideToggle関数を定義
まず要素を滑らかに表示/非表示にするための関数を定義していきます。
jQueryを使ったことがある方はわかると思いますが、slideUp
, slideDown
, slideToggle
という便利なメソッドがありますね。
これらを素のJavaScriptで再現したものが以下のコードになります。
// 要素をスライドしながら非表示にする関数(jQueryのslideUpと同じ)
const slideUp = (el, duration = 300) => {
el.style.height = el.offsetHeight + "px";
el.offsetHeight;
el.style.transitionProperty = "height, margin, padding";
el.style.transitionDuration = duration + "ms";
el.style.transitionTimingFunction = "ease";
el.style.overflow = "hidden";
el.style.height = 0;
el.style.paddingTop = 0;
el.style.paddingBottom = 0;
el.style.marginTop = 0;
el.style.marginBottom = 0;
setTimeout(() => {
el.style.display = "none";
el.style.removeProperty("height");
el.style.removeProperty("padding-top");
el.style.removeProperty("padding-bottom");
el.style.removeProperty("margin-top");
el.style.removeProperty("margin-bottom");
el.style.removeProperty("overflow");
el.style.removeProperty("transition-duration");
el.style.removeProperty("transition-property");
el.style.removeProperty("transition-timing-function");
el.classList.remove("is-open");
}, duration);
};
// 要素をスライドしながら表示する関数(jQueryのslideDownと同じ)
const slideDown = (el, duration = 300) => {
el.classList.add("is-open");
el.style.removeProperty("display");
let display = window.getComputedStyle(el).display;
if (display === "none") {
display = "block";
}
el.style.display = display;
let height = el.offsetHeight;
el.style.overflow = "hidden";
el.style.height = 0;
el.style.paddingTop = 0;
el.style.paddingBottom = 0;
el.style.marginTop = 0;
el.style.marginBottom = 0;
el.offsetHeight;
el.style.transitionProperty = "height, margin, padding";
el.style.transitionDuration = duration + "ms";
el.style.transitionTimingFunction = "ease";
el.style.height = height + "px";
el.style.removeProperty("padding-top");
el.style.removeProperty("padding-bottom");
el.style.removeProperty("margin-top");
el.style.removeProperty("margin-bottom");
setTimeout(() => {
el.style.removeProperty("height");
el.style.removeProperty("overflow");
el.style.removeProperty("transition-duration");
el.style.removeProperty("transition-property");
el.style.removeProperty("transition-timing-function");
}, duration);
};
// 要素をスライドしながら交互に表示/非表示にする関数(jQueryのslideToggleと同じ)
const slideToggle = (el, duration = 300) => {
if (window.getComputedStyle(el).display === "none") {
return slideDown(el, duration);
} else {
return slideUp(el, duration);
}
};
これらを簡単に説明すると、
slideUp
…要素を非表示 & 要素からis-open
クラスを除去slideDown
…要素を表示 & 要素にis-open
クラスを付与slideToggle
…要素が非表示の場合はslideDown
、表示されている場合はslideUp
を返す
という機能になります。
これに関しては全て説明すると長くなるので、解説記事を書きました。
理解を深めたい方はぜひ以下の記事に目を通してみてください。
JSパート2. DOM操作
ここではパート1で定義した関数を使って、アコーディオンを実装していきます。
DEMO1, DEMO2は共通のコードを使用しますが、DEMO3は少し異なるので、順番に説明していきます。
DEMO1 & DEMO2のJavaScriptコード
// アコーディオンを全て取得
const accordions = document.querySelectorAll(".js-accordion");
// 取得したアコーディオンをArrayに変換(IE対策)
const accordionsArr = Array.prototype.slice.call(accordions);
accordionsArr.forEach((accordion) => {
// Triggerを全て取得
const accordionTriggers = accordion.querySelectorAll(".js-accordion-trigger");
// TriggerをArrayに変換(IE対策)
const accordionTriggersArr = Array.prototype.slice.call(accordionTriggers);
accordionTriggersArr.forEach((trigger) => {
// Triggerにクリックイベントを付与
trigger.addEventListener("click", () => {
// '.is-active'クラスを付与or削除
trigger.classList.toggle("is-active");
// 開閉させる要素を取得
const content = trigger.querySelector(".accordion__content");
// 要素を展開or閉じる
slideToggle(content);
});
});
});
こちらがDEMO1とDEMO2共通のコードです。
まずはもう一度実装DEMOを確認してみましょう。
DEMO1のアコーディオン
- DEMO1のアコーディオンタイトル
- ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
- DEMO1のアコーディオンタイトル
- ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
- DEMO1のアコーディオンタイトル
- ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
DEMO2のアコーディオン
- DEMO2のアコーディオンタイトル
- ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
- DEMO2のアコーディオンタイトル
- ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
- DEMO2のアコーディオンタイトル
- ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
DEMOが確認できたので、コードの解説をしていきます。
1~4行目
// アコーディオンを全て取得
const accordions = document.querySelectorAll(".js-accordion");
// 取得したアコーディオンをArrayに変換(IE対策)
const accordionsArr = Array.prototype.slice.call(accordions);
まずはアコーディオンをJavaScriptで操作するために、querySelectorAll
で全て取得します。
次に取得したデータの型をNodeList
からArray
に変換します。
IEなどの古いブラウザでは、NodeList
に対してforEach
メソッドが使用できないため、その対策ですね。
6行目
accordionsArr.forEach((accordion) => {
// 中身は省略
});
1~4行目で全てのアコーディオンを取得できたので、個々のアコーディオンに対して処理をするためにforEach
メソッドを使用します。
7~10行目
// Triggerを全て取得
const accordionTriggers = accordion.querySelectorAll(".js-accordion-trigger");
// TriggerをArrayに変換(IE対策)
const accordionTriggersArr = Array.prototype.slice.call(accordionTriggers);
HTMLパートで、クリックする要素に対してjs-accordion-trigger
クラスを持つdiv
を作成しましたね。
これらをquerySelectorAll
で全て取得します。
また、次でforEach
を使うので、先ほどと同様にNodeList
からArray
に変換します。
12行目
accordionTriggersArr.forEach((trigger) => {
// 個々のTriggerに対しての処理
});
Array
に変換したTriggerに対して個別に処理をするために、forEach
メソッドを使用します。
13~21行目
// Triggerにクリックイベントを付与
trigger.addEventListener("click", () => {
// '.is-active'クラスを付与or削除
trigger.classList.toggle("is-active");
// 開閉させる要素を取得
const content = trigger.querySelector(".accordion__content");
// 要素を展開or閉じる
slideToggle(content);
});
ここでは個々のTriggerに対して、addEventListener
でクリックイベントを登録します。
Triggerをクリックすると、以下の二つの処理が行われます。
- Triggerに
is-active
クラスを付与するor取り除く - コンテンツを表示or非表示にする
まずクラスの付け外しですが、classList.toggle
を使用してis-active
クラスがあるなら取り除く、ないなら付与することができます。
次にコンテンツの表示/非表示に関しては、最初に準備したslideToggle
関数を使用します。
この関数では、
- 要素が表示されている場合…非表示にする
- 要素が非表示の場合…表示する
という風に動作しますね。
これでクリックするたびにアコーディオンが開閉するようになりました!
DEMO3のJavaScriptコード
// アコーディオンを全て取得
const accordions = document.querySelectorAll(".js-accordion");
// 取得したアコーディオンをArrayに変換(IE対策)
const accordionsArr = Array.prototype.slice.call(accordions);
accordionsArr.forEach((accordion) => {
// Triggerを全て取得
const accordionTriggers = accordion.querySelectorAll(".js-accordion-trigger");
// TriggerをArrayに変換(IE対策)
const accordionTriggersArr = Array.prototype.slice.call(accordionTriggers);
accordionTriggersArr.forEach((trigger) => {
// Triggerにクリックイベントを付与
trigger.addEventListener("click", (e) => {
accordionTriggersArr.forEach((trigger) => {
// クリックしたアコーディオン以外を全て閉じる
if (trigger !== e.target.parentElement) {
trigger.classList.remove("is-active");
const openedContent = trigger.querySelector(".accordion__content");
slideUp(openedContent);
}
});
// '.is-active'クラスを付与or削除
trigger.classList.toggle("is-active");
// 開閉させる要素を取得
const content = trigger.querySelector(".accordion__content");
// 要素を展開or閉じる
slideToggle(content);
});
});
});
こちらがDEMO3の実装コードです。
- DEMO3のアコーディオンタイトル
- ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
- DEMO3のアコーディオンタイトル
- ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
- DEMO3のアコーディオンタイトル
- ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
DEMO3では、一度に一つだけ開ける仕様になっていますね。
DEMO1・DEMO2と共通のコードがほとんどなので、違いがある部分のみ解説していきます!
14行目
trigger.addEventListener("click", (e) => {
// 個々のTriggerに対しての処理
});
先ほどとの違いは、関数の第一引数にevent
オブジェクトを渡しているところです。
このイベントオブジェクトe
を通して、クリックした要素に関する情報を得ることができます。
15~22行目
accordionTriggersArr.forEach((trigger) => {
// クリックしたアコーディオン以外を全て閉じる
if (trigger !== e.target.parentElement) {
trigger.classList.remove("is-active");
const openedContent = trigger.querySelector(".accordion__content");
slideUp(openedContent);
}
});
ここでは、クリックしたアコーディオン以外を閉じるという処理を行なっています。
これを実装するには、まずクリックしたTriggerとそれ以外のTriggerを判別する必要がありますね。
まずe.target
で、クリックした要素を取得します。
さらにparentElement
プロパティを使うことで、その親要素であるTriggerを取得できます。
これで、クリックしたTriggerを判別することができました。
if文の条件式では、“クリックしたTrigger以外の全てのTriggerに括弧内のコードを実行する”という指定をします。
if文の括弧内の指示は、以下のとおりです。
- Triggerから
is-active
クラスを取り除く slideUp
関数を呼び出し、既に開いているコンテンツを閉じる
これで、開いているアコーディオンがある場合はまず閉じるという動作を実装することができました。
あとはDEMO1のコードと同じで、クリックしたアコーディオンを開閉する処理を書き入れて完成です。
【まとめ】jQueryなしでアニメーション付きアコーディオンを実装する方法
JavaScriptでアニメーション付きアコーディオンを実装する方法を紹介しました。
素のJSでアコーディオンを実装する方法はいくつか紹介されていますが、その多くはアニメーションがぎこちなかったり、表示が崩れるなど問題点が多いです…。
この記事ではjQueryのスライド系メソッドをJavaScriptに変換して使用しているので、綺麗なアニメーションのアコーディオンを実装することが可能です!
アコーディオンはWeb制作の案件でもよく使う機能なので、ぜひこの記事で紹介したコードを使ってみてください。
今回使用したslide系関数については以下の記事で解説していますので、さらに理解を深めたい方は以下のリンクからどうぞ。
コメント