JavaScript - アコーディオンメニューの作り方
シンプルなアコーディオンメニューを Javascript で作ります。複数のメニューを開いたままにできるタイプと、開けるのは一つのメニューだけで他の項目は自動的に閉じるタイプの2種類のコードを紹介します。
アコーディオンメニューとは?
アコーディオンメニューは、選択した項目の詳細内容を開閉することができるメニューのことです。見出しとなるメニューをクリックすると、そのコンテンツが表示または非表示になります。ウェブサイトなどでよく見られますよね。
今回は、JavaScript を使って2タイプのアコーディオンメニューを作っていきます。記事の前半では、複数の項目を開くアコーディオンメニューのコードを解説し、後半からは一つの項目だけを開くコードを追加します。
この記事を読むと分かること
querySelectorAll()
forEach()
addEventListener()
classList.toggle()
classList.contains()
classList.remove()
nextElementSibling
プロパティscrollHeight
プロパティif…else
条件分岐this
キーワード
サンプルプロジェクト
複数のメニューを開くことができるアコーディオンメニューは、次のような動きになります。
- メニューをクリックするとそのコンテンツが開く。右端のアイコンは「×」に変化する。
- 再びメニューをクリックするとそのコンテンツが閉じる。右端のアイコンは「+」に変化する。
一つのメニューだけを開くアコーディオンメニューは、次のような動きになります。
- メニューをクリックすると開く。
- 他のメニューをクリックすると、現在開いているメニューが閉じ、クリックしたメニューが開く。
- 選択されているメニューをクリックすると閉じる。
HTML
まずは HTML です。今回は、見出しとなるメニューを <button>
要素で三つ用意します。また、「開く・閉じる」を表すアイコンを <span>
要素で追加しています。
<h1>Accordion Menu</h1>
<div class="accordion-container">
<!-- メニュー1 -->
<div class="accordion">
<!-- メニューボタン -->
<button class="menu-button">Menu 1<span class="icon">+</span></button>
<!-- コンテンツ -->
<div class="content">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</div>
</div>
<!-- メニュー2 -->
<div class="accordion">
<!-- メニューボタン -->
<button class="menu-button">Menu 2<span class="icon">+</span></button>
<!-- コンテンツ -->
<div class="content">
<p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
</div>
</div>
<!-- メニュー3 -->
<div class="accordion">
<!-- メニューボタン -->
<button class="menu-button">Menu 3<span class="icon">+</span></button>
<!-- コンテンツ -->
<div class="content">
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
</div>
</div>
</div>
CSS
次は CSS です。CSS でポイントとなるのは、次の2点です。
- アイコン「+」は、メニューボタンに
open
クラスが追加されたときに、transform
プロパティを指定して「×」へ回転するようにします。
(open
クラスの追加/削除は、この後の JavaScript で行います) - コンテンツは、
height
とoverflow
プロパティを指定し、閉じた状態にします。
(コンテンツを開くための高さheight
は、この後の Javascript で取得します)
body {
background-color: #0D7DF5;
}
h1 {
color: #ffffff;
text-align: center;
margin-bottom: 30px;
}
.accordion-container {
width: 90%;
max-width: 500px;
margin: 0 auto;
}
.menu-button {
width: 100%;
padding: 10px 20px;
margin-top: 2px;
font-size: 18px;
display: flex;
justify-content: space-between;
align-items: center;
border: none;
background-color: #e6e6e6;
cursor: pointer;
}
.menu-button:hover {
background-color: #ffffff;
}
.icon {
font-size: 20px;
transition: 0.4s;
}
/* メニューボタンにopenクラスがついているときの
アイコンのスタイル「×」 */
.menu-button.open .icon {
transform: rotate(45deg);
}
.content {
padding: 0 20px;
background-color: #f2f2f2;
transition: 0.4s;
/* コンテンツを閉じる */
height: 0;
overflow: hidden;
}
JavaScript
ではここから、JavaScript を書いていきましょう。最初に、「複数のメニューを開くタイプ」のアコーディオンメニューを解説します。後ほど解説する「一つのメニューだけを開くタイプ」も、このコードがベースになりますよ。
すべてのメニューボタンにクリックイベントを追加する
JavaScript で最初にすることは、メニューボタンをクリックできるようにすることです。そのためのコードを書いていきましょう。
- すべてのメニューボタンを
querySelectorAll()
メソッドでまとめて取得し、変数menuBtns
に保存します。
// すべてのメニューボタンを取得
const menuBtns = document.querySelectorAll('.menu-button');
- すべてのメニューボタンをクリックできるようにするために、
forEach()
メソッドでメニューボタン一つひとつに処理を行います。
const menuBtns = document.querySelectorAll('.menu-button');
// メニューボタン一つひとつに処理を行う
menuBtns.forEach(menuBtn => {
// メニューボタンにクリックイベントを追加
// …
});
addEventListener()
メソッドで、メニューボタンがクリックされたときに関数が実行されるようにします。
const menuBtns = document.querySelectorAll('.menu-button');
menuBtns.forEach(menuBtn => {
// メニューボタンにクリックイベントを追加
menuBtn.addEventListener('click', function() {
// クリックされたときに実行する処理
// …
});
});
クリックされたときに実行する関数は、アロー関数ではなく function
キーワードを使って書いてください。その理由は、後ほど説明します。
アイコンの見た目を変化させる
メニューボタンをクリックできるようになったら、クリックされたときに実行する関数を書いていきましょう。
まずは、アイコンの見た目を切り替えます。アイコンを「+」と「×」で変化させるためには、classList.toggle()
メソッドで open
クラスの追加/削除を行います。
- クリックされたメニューボタンに、
open
クラスの付け外しをします。
const menuBtns = document.querySelectorAll('.menu-button');
menuBtns.forEach(menuBtn => {
menuBtn.addEventListener('click', function() {
// openクラスの追加または削除
this.classList.toggle('open');
});
});
イベントハンドラー内で使われている this
は、イベントが発生して関数を実行している要素(クリックされたメニューボタン)を参照します。クリックされたときに実行する関数を function
キーワードを使って書くのは、この this
を有効にするためです。(アロー関数では、この this
は正しく動きません)
メニューボタンをクリックすると open
クラスの追加/削除が切り替わるので、アイコンの見た目もクリックのたびに「+」と「×」で変化するようになりました。
コンテンツを開閉する
次は、クリックされたメニューのコンテンツを開閉できるようにします。該当のコンテンツを取得するために使うのは、次の兄弟要素を指す nextElementSibling
プロパティです。
兄弟要素とは、同じ親を持つ要素のことです。メニューボタンと同じ親を持ち、メニューボタン次にある要素は、そのコンテンツですね。
メニュー1がクリックされたらメニュー1のコンテンツを、メニュー2がクリックされたらメニュー2のコンテンツを取得できるようにしましょう。
- クリックされたメニューボタン
this
の次の兄弟要素を取得して、変数content
に格納します。
const menuBtns = document.querySelectorAll('.menu-button');
menuBtns.forEach(menuBtn => {
menuBtn.addEventListener('click', function() {
this.classList.toggle('open');
// クリックされたメニューボタンのコンテンツを取得
const content = this.nextElementSibling;
});
});
続いて、コンテンツを開くのか閉じるのか、 if…else
で処理を分けます。classList.contains()
メソッドを使い、メニューボタンに open
クラスが含まれているかどうかを調べましょう。
- もし、クリックされたメニューボタンに
open
クラスが含まれているなら
const menuBtns = document.querySelectorAll('.menu-button');
menuBtns.forEach(menuBtn => {
menuBtn.addEventListener('click', function() {
this.classList.toggle('open');
const content = this.nextElementSibling;
// openクラスが含まれているかどうかによってコンテンツを開くまたは閉じる
if (this.classList.contains('open')) {
// 開く
} else {
// 閉じる
}
});
});
条件を満たす場合は、コンテンツを開きます。コンテンツは、CSS でheight: 0
にしましたね。高さが 0
のままだと表示されないので、scrollHeight
プロパティでコンテンツの高さを取得し、その値をコンテンツの高さ height
とします。
- コンテンツの高さを取得して、コンテンツを開きます。
const menuBtns = document.querySelectorAll('.menu-button');
menuBtns.forEach(menuBtn => {
menuBtn.addEventListener('click', function() {
this.classList.toggle('open');
const content = this.nextElementSibling;
if (this.classList.contains('open')) {
// コンテンツを開く
content.style.height = content.scrollHeight + 'px';
} else {
// 閉じる
}
});
});
クリックされたメニューボタンに open
クラスが含まれていない場合は、コンテンツを閉じましょう。
- コンテンツの高さを
0
にして、コンテンツを閉じます。
const menuBtns = document.querySelectorAll('.menu-button');
menuBtns.forEach(menuBtn => {
menuBtn.addEventListener('click', function() {
this.classList.toggle('open');
const content = this.nextElementSibling;
if (this.classList.contains('open')) {
content.style.height = content.scrollHeight + 'px';
} else {
// コンテンツを閉じる
content.style.height = 0;
}
});
});
以上で、複数のメニューを開くタイプのアコーディオンメニューのコードが完成です。
他のメニューが開いていたら閉じる
ここからは、一つのメニューだけを開くためのコードを追加していきましょう。クリックしたメニュー以外の項目が開いている場合は、自動的に閉じるようにしますよ。
const menuBtns = document.querySelectorAll('.menu-button');
menuBtns.forEach(menuBtn => {
menuBtn.addEventListener('click', function() {
// 一つのメニューだけを開くためのコードをここに追加
// …
//-----------------------------------------------
this.classList.toggle('open');
const content = this.nextElementSibling;
if (this.classList.contains('open')) {
content.style.height = content.scrollHeight + 'px';
} else {
content.style.height = 0;
}
});
});
先に解説したコードでは、「クリックされたメニューボタン」にだけ注目して処理を行いました。これから追加するコードで注目するのは、「開いているメニュー」です。メニューボタンに open
クラスが付いているのが、開いているメニューですね。
- 開いているメニューを取得して、変数
activeAccordion
に格納します。
const menuBtns = document.querySelectorAll('.menu-button');
menuBtns.forEach(menuBtn => {
menuBtn.addEventListener('click', function() {
// 開いているメニューを取得
const activeAccordion = document.querySelector('.menu-button.open');
//-----------------------------------------------
this.classList.toggle('open');
const content = this.nextElementSibling;
if (this.classList.contains('open')) {
content.style.height = content.scrollHeight + 'px';
} else {
content.style.height = 0;
}
});
});
場合によっては、メニューがすべて閉じていることもあるため、この後の処理には if
文で条件をつけますよ。クリックしたメニュー以外に開いている項目がある場合だけ、それを閉じる処理を実行します。
- 開いているメニューがあって、かつ、それがクリックしたメニューではないなら
const menuBtns = document.querySelectorAll('.menu-button');
menuBtns.forEach(menuBtn => {
menuBtn.addEventListener('click', function() {
const activeAccordion = document.querySelector('.menu-button.open');
// クリックしたメニュー以外に開いている項目があるなら
if(activeAccordion && activeAccordion !== this) {
// 閉じる
// …
}
//-----------------------------------------------
this.classList.toggle('open');
const content = this.nextElementSibling;
if (this.classList.contains('open')) {
content.style.height = content.scrollHeight + 'px';
} else {
content.style.height = 0;
}
});
});
nextElementSibling
プロパティで開いているメニューのコンテンツを取得し、その高さを0
にして閉じます。classList.remove()
メソッドでopen
クラスを削除し、アイコンを「+」に戻します。
const menuBtns = document.querySelectorAll('.menu-button');
menuBtns.forEach(menuBtn => {
menuBtn.addEventListener('click', function() {
const activeAccordion = document.querySelector('.menu-button.open');
if(activeAccordion && activeAccordion !== this) {
// 開いているメニューを閉じる
activeAccordion.nextElementSibling.style.height = 0;
// アイコンを+にする
activeAccordion.classList.remove('open');
}
//-----------------------------------------------
this.classList.toggle('open');
const content = this.nextElementSibling;
if (this.classList.contains('open')) {
content.style.height = content.scrollHeight + 'px';
} else {
content.style.height = 0;
}
});
});
以上で、一つのメニューだけを開くためのコードを追加できました。
完成コード
2タイプのアコーディオンメニューのコードが完成です。
//*****************************
// アコーディオンメニューのコード
//*****************************
const menuBtns = document.querySelectorAll('.menu-button');
menuBtns.forEach(menuBtn => {
menuBtn.addEventListener('click', function() {
//----- 一つだけ開く場合に追加するコード --------------
const activeAccordion = document.querySelector('.menu-button.open');
// クリックしたメニュー以外に開いている項目があるなら閉じる
if(activeAccordion && activeAccordion !== this) {
activeAccordion.nextElementSibling.style.height = 0;
activeAccordion.classList.remove('open');
}
//------------------------------------------------
// クリックしたメニューを開くまたは閉じる
this.classList.toggle('open');
const content = this.nextElementSibling;
if (this.classList.contains('open')) {
content.style.height = content.scrollHeight + 'px';
} else {
content.style.height = 0;
}
});
});
See the Pen JavaScript - Accordion Menu by Pyxofy (@pyxofy) on CodePen.
Pyxofy (著)「きょうからはじめるスクラッチプログラミング入門」
Pyxofy が Scratch の電子書籍を出版しました!Kindle・Apple Books からご購入ください。
まとめ
今回は、JavaScriptでアコーディオンメニューを作成しました。
メニューを開いたり閉じたりするために行ったのは、クラスの付け外しです。それによって、「開く・閉じる」のアイコンの見た目を変化させたり、コンテンツの表示・非表示を切り替えました。
最後まで読んでいただき、ありがとうございます。この記事をシェアしてくれると嬉しいです!
SNSで Pyxofy とつながりましょう! LinkedIn・ Threads・ Mastodon・ X (Twitter) @pyxofy・ Facebook