大阪でRailsを中心に学習している薬剤師エンジニア(初学者)こと、ヨマ(@yoma_2003)です!
JavaScriptで複数のアコーディオンパネルを作成する方法をまとめます。
※おことわり※
断定口調でまとめますが、初学者であるため間違い等あればご指摘頂けると嬉しいです。
はじめに
今回作成したい目標物は、以下の様な複数のアコーディオンパネル(クリックで開閉する要素)です。
特徴
- クリックするとクリックしたBoxが開く
- 別のBoxをクリックすると開いていたBoxが閉じ、クリックしたBoxが開く(常に開くのは1つだけ)
- Box以外をクリックすると開いていたBoxが閉じる
結論
<div class="main_contents">
<div class="box">
<h3>Box1</h3>
<p>クリックで内容表示</p>
<div class="box_content">
内容を記入します。内容を記入します。内容を記入します。内容を記入します。内容を記入します。内容を記入します。
</div>
</div>
<div class="box">
<h3>Box2</h3>
<p>クリックで内容表示</p>
<div class="box_content">
内容を記入します。内容を記入します。内容を記入します。内容を記入します。内容を記入します。内容を記入します。
</div>
</div>
<div class="box">
<h3>Box3</h3>
<p>クリックで内容表示</p>
<div class="box_content">
内容を記入します。内容を記入します。内容を記入します。内容を記入します。内容を記入します。内容を記入します。
</div>
</div>
<div class="box">
<h3>Box4</h3>
<p>クリックで内容表示</p>
<div class="box_content">
内容を記入します。内容を記入します。内容を記入します。内容を記入します。内容を記入します。内容を記入します。
</div>
</div>
</div>
.main_contents {
margin: 50px auto;
width: 800px;
display: flex;
justify-content: space-between;
}
.box {
height: 100px;
width: 180px;
margin: 0 10px;
padding: 5px;
text-align: center;
overflow: hidden;
border: 1px solid skyblue;
border-radius: 3px;
transition: .2s;
cursor: pointer;
}
.box:hover {
box-shadow: 0px 0px 7px #aaa;
}
.box_content {
margin-top: 20px;
}
.open { /* Boxが開いた場合に付与するクラス */
height: 260px;
}
const accordionBox = () => {
isOpen = false;
window.addEventListener("click", function(e) {
if (isOpen == true && (e.target.closest('.box') == null)) { // いずれかのBoxが開いた状態で、Box以外をクリック
const OpenedBox = document.querySelector(".open");
OpenedBox.classList.remove("open");
isOpen = false;
}
});
const boxes = document.querySelectorAll(".box");
boxes.forEach(function(box) {
box.addEventListener("click", function(e) {
if (this.classList.contains("open") == true) { // 開いているBox自身をクリック
return;
} else if (isOpen == true) { // 開いているBox以外のBoxをクリック
const OpenedBox = document.querySelector(".open");
OpenedBox.classList.remove("open");
this.classList.add("open");
} else { // いずれのBoxも開いていない状態で、Boxをクリック
this.classList.add("open");
}
isOpen = true;
})
});
};
window.addEventListener("load", accordionBox);
解説
今回の目標物の動作のポイントは、クリックしたときの条件分岐です。
クリックしたときの状態は大きく分けて2つその上で定義する必要がある条件は以下の4つです。
条件分岐
- Box以外をクリック
- いずれかのBoxが開いている状態
- Boxをクリック
- 開いているBox自身をクリック
- 開いているBox以外のBoxをクリック
- いずれのBoxも開いていない状態
これらの条件は以下のように定義できます。
いずれかのBoxが開いている状態でBox以外をクリック
isOpen = false; // Boxの状態を定義(デフォルトはfalse)
window.addEventListener("click", function(e) {
if (isOpen == true && (e.target.closest('.box') == null)) {処理}
});
まずisOpen == true
とすることで「いずれかのBoxが開いている状態」を定義します。
その上で、window
のクリックイベントをe
で取得し、e.target
とすることでクリックイベントが発生した要素を取得します。
このクリックされた要素に対し、.closest('.box')
とすることで、この要素とその親に、'.box'
というノードがあるか探索します。
== null
とすることで、クリックした要素にboxクラスがない場合、つまり「Box要素以外をクリックした状態」を定義することができます。
開いているBox自身をクリック
const boxes = document.querySelectorAll(".box"); // 全てのBoxを取得
boxes.forEach(function(box) { //それぞれのBoxに対して処理
box.addEventListener("click", function(e) {
if (this.classList.contains("open") == true) {処理}
・・・
まず全てのBox
を取得しforEach
を使うことでそれぞれのBox
に対して処理を定義します。
this
(クリックされたBox要素)に対して.classList.contains("open")
とすることで、この要素がopenクラスを含むかを判断します。
== true
とすることで「開いているBox自身をクリックした状態」を定義することができます。
開いているBox以外のBoxをクリック
・・・
else if (isOpen == true) {処理}
・・・
isOpen == true
なのでいずれかのBoxが開いている状態を定義でき、かつ先ほどの「開いているBox自身をクリックした状態」以外に当たるので、「開いているBox以外のBoxをクリックした状態」を定義できます。
いずれのBoxも開いていない状態で、Boxをクリック
・・・
else {処理}
・・・
先ほどのisOpen == true
という条件以外にあたるので、「いずれのBoxも開いていない状態で、Boxをクリックした状態」が定義できます。
openクラスの付与とisOpenの状態変更でパネルの開閉を管理
Element.classList.add("open")
条件分岐した中で.classList
メソッドを使用してopenクラスをつけ外ししてBox要素を開閉、isOpen
という変数でBox開閉状態を変更し管理します。
おわりに
要素の開閉自体はもう少し簡単に定義できると思いますが、これらの条件分岐を定義することで、Box開閉だけでなく更に処理を追加することもできます。
参考