2020.2.12

【アニメーション&複数対応】アコーディオンメニューをJavaScriptで作る(サンプルコードあり)

はじめに

この記事では、以下条件に対応出来るアコーディオンメニューの作り方を解説します。

  • JavaScriptのみを使用(jQueryは使わない)
  • 表示非表示の切り替えがアニメーション(jQueryでいうslideDown的なイメージ)
  • アコーディオンする要素は「ひとつ」でも「複数」でもどちらも対応可能
  • 同一ページで複数の要素に対してアコーディオンを設定出来る

動作サンプル

デモはこちら

 

実際のコード

まずは、サンプルコードを下記に載せます。

JavaScript、HTML、CSSをコピーすれば、そのまま使えます。

JavaScript

function Accordion(obj) {

  // `wrap_id`で指定した要素が無かったら、以降は実行しない
  if (!document.querySelector(`#${obj.wrap_id}`)) {
    return false
  }

  // 関数呼び出し時の引数をもとに変数を定義
  const ElmItems = document.querySelectorAll(`#${obj.wrap_id} .${obj.item_class}`)
  const ClassAccordionElm = obj.accordion_class
  const ElmAccordions = document.querySelectorAll(`#${obj.wrap_id} .${ClassAccordionElm}`)
  const Transition = obj.accordion_transition ? obj.accordion_transition : '.4s'
  const AttrToggle = obj.toggle_attr ? obj.toggle_attr : 'data-active'
  const DefaultOpen = obj.default_is_open ? obj.default_is_open : false
  let arryAccordionElmHeight = new Array()

  // アコーディオンさせる要素の高さをそれそれ取得して配列に格納
  // その後、`default_is_open`が`false`なら高さを0にする
  Array.from(ElmAccordions).forEach(element => {
    element.style.height = 'auto'
    element.style.overflow = 'hidden'
    element.style.transition = Transition
    arryAccordionElmHeight.push(element.clientHeight)
    if (!DefaultOpen) {
      element.style.height = '0'
    }
  })

  Array.from(ElmItems).forEach((element, index) => {
    // `default_is_open`が`true`なら、
    //     `item_class`にアコーディオンが開いている時に付与する`toggle_attr`の属性を設定する
    //     (アコーディオンが閉じている時は、`toggle_attr`の属性は削除される)
    //     アコーディオンする要素に`height`を設定する(autoから0にすると`transition`を設定してもアニメーションしないから、`height`を数値で設定する)
    if (DefaultOpen) {
      element.setAttribute(AttrToggle, '')
      ElmAccordions[index].style.height = `${arryAccordionElmHeight[index]}px`
    }

    // `item_class`に`addEventListener`を設定
    element.addEventListener('click', (event) => {

      // クリック時の要素と属性の有無を取得
      // (クリック時に`toggle_attr`の属性を付け外しすることで、アコーディオンが開いているかどうかを判別する)
      let target = event.currentTarget
      let hasAttr = target.hasAttribute(AttrToggle)
      let elmAccordion

      // アコーディオンする要素を取得して、`elmAccordion`に格納
      target.childNodes.forEach(childItem => {
        if (childItem.className == ClassAccordionElm) {
          elmAccordion = childItem
        }
      })

      // `toggle_attr`の属性の有無でアコーディオンを「開く」か「閉じる」か、どちらかの処理を実行
      if (hasAttr) {
        target.removeAttribute(AttrToggle)
        elmAccordion.style.height = '0'
      } else {
        target.setAttribute(AttrToggle, '')
        elmAccordion.style.height = `${arryAccordionElmHeight[index]}px`
      }

    }) // / addEventListener
  }) // / forEach

}

// ここで関数を呼び出し
Accordion({
  wrap_id: 'first-accordion',
  item_class: 'p-accordion__item',
  accordion_class: 'p-accordion__content',
  accordion_transition: '.4s',
  toggle_attr: 'data-active',
  default_is_open: true,
})

HTML

<ul class="p-accordion" id="first-accordion">
  <li class="p-accordion__item">
    <p class="p-accordion__head">
      <a href="javascript:void(0);">1つ目</a>
    </p>
    <div class="p-accordion__content">
      <p>text</p><p>text</p><p>text</p>
    </div>
  </li>
  <li class="p-accordion__item">
    <p class="p-accordion__head">
      <a href="javascript:void(0);">2つ目</a>
    </p>
    <div class="p-accordion__content">
      <p>text</p><p>text</p><p>text</p>
      <p>text</p><p>text</p><p>text</p>
    </div>
  </li>
  <li class="p-accordion__item">
    <p class="p-accordion__head">
      <a href="javascript:void(0);">3つ目</a>
    </p>
    <div class="p-accordion__content">
      <p>text</p><p>text</p><p>text</p>
      <p>text</p><p>text</p><p>text</p>
      <p>text</p><p>text</p><p>text</p>
    </div>
  </li>
</ul>

CSS

*{
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

li{
  list-style: none;
}

#wrap{
  width: 900px;
  padding: 40px 40px;
  margin: auto;
}

.p-accordion{
  display: flex;
  justify-content: space-between;
}

.p-accordion__item{
  width: 32%;
}

.p-accordion__head a{
  display: block;
  color: #fff;
  text-decoration: none;
  background-color: #333;
  padding: 8px 12px;
  position: relative;
}

.p-accordion__head a::before,
.p-accordion__head a::after{
  content: "";
  display: block;
  width: 16px;
  height: 2px;
  background-color: #fff;
  position: absolute;
  top: 0;
  bottom: 0;
  right: 12px;
  margin: auto 0;
  transition: .2s;
}

.p-accordion__head a::before{
  transform: rotate(90deg);
}

.p-accordion__item[data-active] .p-accordion__head a::before{
  transform: rotate(270deg);
  opacity: 0;
}

.p-accordion__item[data-active] .p-accordion__head a::after{
  transform: rotate(180deg);
}

.p-accordion__content{
  margin-top: 8px;
  padding: 0 12px;
}

使い方

関数Accordion()を呼び出し時に引数をオブジェクト形式で渡します。
jQueryのプラグイン呼び出しのようなイメージです。
サンプルコードの下記の部分が、関数を呼び出している箇所です。
Accordion({
  wrap_id: 'first-accordion',
  item_class: 'p-accordion__item',
  accordion_class: 'p-accordion__content',
  accordion_transition: '.4s',
  toggle_attr: 'data-active',
  default_is_open: true,
})

Accordion({
  キー:値,
  キー:値,
  キー:値,
})
実際にご自身のサイトで使う場合、上記の「値」を各々設定してください。
キー
wrap_idアコーディオンを囲っている親要素のID
item_classアコーディオンが複数ある場合、その繰り返されている要素のclass
accordion_class実際に表示非表示が切り替わる要素のclass
accordion_transition表示非表示が切り替わる際のアニーションのスピード
デフォルト:'.4s'
toggle_attrアコーディオンする要素が表示の時に、item_classに付与される属性名
デフォルト:'data-active'
default_is_openページ読み込み時に、アコーディオンする要素を表示の状態にするかどうか
デフォルト:false(非表示)

 

注意点

wrap_iditem_classaccordion_classで値を設定する際、それぞれ別の要素としてIDとclassを設定して下さい。

<!-- OK -->
<div id="wrap_id">
  <div class="item_class">
    <p class="accordion_class">text</p>  
  </div>
</div>

<!-- NG -->
<!-- wrap_id と item_class が同じ要素に設定されているからNG -->
<div id="wrap_id" class="item_class">
  <p class="accordion_class">text</p>  
</div>

 

簡単な解説

今回のサンプルコードは、ES6の「テンプレート文字列」の書き方を使っているので、コンパイル環境が無い場合は適所書き換えて下さい。

変数と文字列を同時を結合する場合、これまでは「+」を使って組み合わせていましたが、「テンプレート文字列」の書き方をすることで、「+」を使わなくても変数と文字列を結合出来ます。

`#${obj.wrap_id} .${obj.item_class}`
↓
'#' + obj.wrap_id + ' .' + obj.item_class

 

関数呼び出し時の引数をもとに、要素などを取得します。

アコーディオンさせる要素の高さを取得して、js側で高さの切り替えを行なっています。

なので、アコーディオンさせる要素が複数あっても、それぞれCSSでheightを設定しなくて大丈夫です。

 

CSSでheightのアニメーションを作る場合、「0pxから100px」のような値の変わり方なら、transitionで滑らかな切り替わりが表現出来ます。

しかし、「autoから100px」のような値の変わり方の場合、transitionを使用しても動きは滑らかになりません。

 

アコーディオンさせる要素が表示の時は、toggle_attrで設定した属性を付与して、非表示の時はその属性は削除されます。

なので、表示、非表示でアイコンの切り替え(プラスとマイナスなど)などを行いたい場合は、toggle_attrで設定した属性が付いている時は○○」のような書き方をすればOKです。

まとめ

今回のサンプルコードには、コメントアウトで出来るだけ処理の説明を残しておきましたので、カスタマイズする際の参考にして頂ければと思います。

このような汎用性の高いサンプルコードをこれからは作っていこうと思うので、その時はまた本ブログにてご紹介させて頂きます。

シェアする
フォローする
Web-Guided - web業界で働く方を少しだけ手助けするメディア