【HTML,CSS】アコーディオンメニューの実装【JavaScript】

どうも、くまだです。

Web制作の案件でよくアコーディオンメニューを実装するので、それのメモ書きみたいなもの。

アコーディオンメニュー

動き的には下記動画で。(矢印の動きは微妙だが…)

コードはこちら。

〇 HTML

  <div class="p-qa">

    <ul class="p-qa__block" itemscope="" itemtype="https://schema.org/FAQPage">
      <li class="c-qa" itemscope="" itemprop="mainEntity" itemtype="https://schema.org/Question">
        <button itemprop="name" class="c-qa__head js-ac">テキストテキストテキストテキスト</button>
        <div itemscope="" itemprop="acceptedAnswer" itemtype="https://schema.org/Answer" class="c-qa__body">
          <p itemprop="text">
            テキストテキストテキストテキスト
          </p>
        </div>
       </li>
      <li class="c-qa" itemscope="" itemprop="mainEntity" itemtype="https://schema.org/Question">
        <button itemprop="name" class="c-qa__head js-ac">テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト</button>
        <div itemscope="" itemprop="acceptedAnswer" itemtype="https://schema.org/Answer" class="c-qa__body">
          <p itemprop="text">
            テキストテキストテキストテキストテキストテキストテキストテキスト
          </p>
        </div>
        </li>
      <li class="c-qa" itemscope="" itemprop="mainEntity" itemtype="https://schema.org/Question">
        <button itemprop="name" class="c-qa__head js-ac">テキストテキストテキストテキスト</button>
        <div itemscope="" itemprop="acceptedAnswer" itemtype="https://schema.org/Answer" class="c-qa__body">
          <p itemprop="text">
            テキストテキストテキストテキスト
          </p>
        </div>
      </li>
    
    </ul>
  </div>

〇 CSS

ul {
  padding: 0;
  margin: 0;
}

p {
  margin: 0;
}

button {
  outline: 0;
  border: none;
}


.p-qa__block {
  display: flex;
  flex-direction: column;
  min-width: 200px;
}


.c-qa {
  display: inline-block;

}

.c-qa__head {

  position: relative;
  text-align: left;
  padding: 20px 30px 20px 20px;
  border-radius: 8px 8px 0 0;
  background: blue;
  color: #ffffff;
  cursor: pointer;
  width: 100%;
}

.c-qa__head:after {
  content: "";
  border-top: 1px solid #fff;
  border-left: 1px solid #fff;
  display: inline-block;
  width: 10px;
  height: 10px;
  transform: rotate(-135deg) translateY(9px);
  position: absolute;
  right: 20px;
  top: 50%;
  transition: transform .4s;

}

.c-qa__body {
  position: relative;
  padding: 0 20px;
  overflow: hidden;
  border-radius: 0 0 8px 8px;
  background: #fff;
  color: #1A236C;
  opacity: 0;
  transition: line-height 0.4s, padding 0.4s, opacity 0.4s;
  border: 1px solid;
  line-height: 0;
  margin: 0;
}

.c-qa__body.is-open {
  padding: 20px;
  line-height: 1.5;
  opacity: 1;
  border-color: blue;
}

.c-qa__head.is-open::after {
  transform: rotate(45deg) translateY(-10px);

}


.c-qa:not(:first-child) {
  margin-top: 16px;
}

〇 JavaScript

const menu = document.querySelectorAll(".js-ac");

function acToggle() {
  const content = this.nextElementSibling;
  content.classList.toggle("is-open");

  const menuAc =this;
  menuAc.classList.toggle('is-open'); 
}

for (let i = 0; i < menu.length; i++) {
  menu[i].addEventListener("click", acToggle);
}

HTMLの、itemscope~とかitemprop=~とかは構造化マークアップ対応のためです。(クライアント様にアコーディオンのところを構造化マークアップで、といわれたときあったので)

構造化マークアップのチェックは下記のサイトで。

開閉前のコンテンツのpaddingの上下0やline-heightが0なのは、ここに数値が入ってしまうと、

.c-qa__body {
  position: relative;
  padding: 20px; /** 開閉前の余白上下に数値をいれた場合  **/
  overflow: hidden;
  border-radius: 0 0 8px 8px;
  background: #fff;
  color: #1A236C;
  opacity: 0;
  transition: line-height 0.4s, padding 0.4s, opacity 0.4s;
  border: 1px solid;
  line-height: 1;/** 開閉前数値をいれた場合  **/
  margin: 0;
}
アコーディオンメニュー

上記画像のように、アコーディオン間に開閉前のコンテンツが(見えないけど)高さがあるままなので余白が広がってしまう。なので、上下余白やline-heightは開閉前は0にして、開いたときに数字を持たせる

.c-qa__body.is-open { /** 開いたときに数字持たせる **/
  padding: 20px;
  line-height: 1.5;
  opacity: 1;
  border-color: blue;
}

なお、.c-qa__bodyの開閉前の左右paddingを0にしないのは、開いたときにテキストが左から右に出るような不自然な動きになってしまうため。

.c-qa__body {
  position: relative;
  padding: 0; /** ここを0にした場合 **/
  overflow: hidden;
  border-radius: 0 0 8px 8px;
  background: #fff;
  color: #1A236C;
  opacity: 0;
  transition: line-height 0.4s, padding 0.4s, opacity 0.4s;
  border: 1px solid;
  line-height: 0;
  margin: 0;
}

テキストの出方に違和感があるので、左右の余白は開閉前も保持したままで。上下の余白は0、左右の余白は保持したままなら開いたとき自然な感じになる。あとはtransitionでうまく調整。

JSは、

const menu = document.querySelectorAll(".js-ac");

でjs-acクラスがついたものすべて取得する。アコーディオンが一つしかないなら、querySelectorでよい。今回は複数あるのでquerySelectorAllで。

クリックしたときの動きを作成。

function acToggle() {
  const content = this.nextElementSibling;
  content.classList.toggle("is-open");

  const menuAc =this;
  menuAc.classList.toggle('is-open'); 
}

nextElementSiblingは「次の要素を取得する」プロパティです。ここの「this」は、js-acクラスがついている要素のことです。(青いコンテンツのところ)

<button itemprop="name" class="c-qa__head js-ac">テキストテキストテキストテキスト</button>

js-acクラスを持ったc-qa__headをクリックしたら、次の要素のc-qa__bodyを取得する。

content.classList.toggle("is-open");

で、取得したc-qa__bodyにis-openのクラスを付与する。toggle()使ってるので、is-openのつけ外し可能。

  const menuAc =this;
  menuAc.classList.toggle('is-open'); 

ここの「this」は、js-acクラスがついたc-qa__head自身。自身にis-openクラスのつけ外しをする。(矢印用)

for (let i = 0; i < menu.length; i++) {
  menu[i].addEventListener("click", acToggle);
}

あとはクリックイベント登録して、クリックした要素に対してacToggle関数を発火させる。

ここまで読んでくださりありがとうございました。

この記事を書いた人