jQueyを使えば、slideToggleなどを使えば簡単に実装できるアコーディオンの動きですが、jQueryなしでとなると、少し面倒くさい。。。

しかし、アコーディオンの動きをつけるためだけに、jQueryを読み込むのは嫌だったので、jQueryを使わずアコーディオンを実装しました。

制作コード

javascript

コードをクリップボードにコピー
// define variable -------------------------
const accbtns = document.getElementsByClassName('js-accbtn');
const acccontents = document.getElementsByClassName('js-acccontent');
const accOpenClass = "open";
const accSpeed = 300;
const accDivisor = 30;
const accInterval = accSpeed/accDivisor;

// define function -------------------------
const accClick= ()=>{
  for(let i = 0; i < accbtns.length ; i++){
    accbtns[i].addEventListener('click',(e)=>{
      const accid  = accbtns[i].getAttribute('data-acc');
      if( e.currentTarget.classList.contains(accOpenClass) ){
        accClose(accid,i);
      }else{
        accOpen(accid,i);
      }
    });
  }
}

const accOpen = (id,i) =>{
  accbtns[i].classList.add(accOpenClass);
  for(let n = 0; n < acccontents.length; n++){
    const accid = acccontents[n].getAttribute('data-acc');
    if( id == accid ){
      const accOpenFirst = (callback)=>{
        acccontents[n].style.display = "block";
        const height = acccontents[n].clientHeight;
        acccontents[n].style.display = "none";
        callback(height);
      }
      accOpenFirst((height) =>{
        const nowH = 0;
        const initCount = 0;
        const minH = height/accDivisor;
        acccontents[n].style.display = "block";
        acccontents[n].style.height = '0px';
        acccontents[n].style.overflow = "hidden";
        accOpenInterval(nowH,minH,n,initCount);
      });
    }
  }
}

const accOpenInterval =(nowH,minH,n,count)=>{
  const nextH = nowH + minH;
  count ++;
  acccontents[n].style.height = `${nextH}px`;
  if( count < accDivisor){
    setTimeout(accOpenInterval,accInterval,nextH,minH,n,count);
  }else{
    acccontents[n].style.overflow = "";
  }
}

const accClose = (id,i) =>{
  accbtns[i].classList.remove(accOpenClass);
  for(let n = 0; n < acccontents.length; n++){
    const accid = acccontents[n].getAttribute('data-acc');
    if( id == accid ){
      const accCloseFirst = (callback)=>{
        const height = acccontents[n].clientHeight;
        acccontents[n].style.overflow = "hidden";
        callback(height);
      }
      accCloseFirst((height)=>{
        const nowH = height;
        const initCount = 0;
        const minH = height/accDivisor;
        accCloseInterval(nowH,minH,n,initCount);
      });
    }
  }
}

const accCloseInterval =(nowH,minH,n,count)=>{
  const nextH = nowH - minH;
  count++;
  acccontents[n].style.height = `${nextH}px`;
  if( count < accDivisor){
    setTimeout(accCloseInterval,accInterval,nextH,minH,n,count);
  }else{
    acccontents[n].style.display = "none";
    acccontents[n].style.height = "";
    acccontents[n].style.overflow = "";
  }
}

// exe function -------------------------
accClick();

IE11に適用させる場合は、以下のスクリプトを読み込んで使用してください。

https://www.promisejs.org/polyfills/promise-7.0.4.min.js

このjsはpromiseを使用しているのですが、IE11ではpromiseが動きません。なので、IE11にも適用させたい場合は、上記のスクリプトは必須になります。

2019/9/4 修正

promiseを使わずにコールバック関数で書き直しましたので、上記のスクリプトを読み込まなくてもIE11で動きます。

ただconstなどを使用しているので、トランスパイルをする必要はあります。
私は以下のツールでトランスパイルしています。

使用しているコンパイルツール「Prepros」使用しているコンパイルツール「Prepros」

html

コードをクリップボードにコピー
<div class="js-accbtn" data-acc="acc1">アコーディオンボタン</div>

<div class="js-acccontent" data-acc="acc1">
  <div class="inner">
    アコーディオンのコンテンツ    
  </div>
</div>

実装例

See the Pen OJLmpXR by takblog (@blanks-site) on CodePen.

使い方

javascriptのコードを改変せずに、そのまま使った場合のマークアップを説明していきます。

htmlのコードを見ながら読みたい人はこちらをクリックして、別ウィンドウでhtmlのコードを出してください。

  1. アコーディオンのボタンにしたい要素には js-accbtn のクラスを付ける。
  2. アコーディオンのコンテンツにしたい要素には js-acccontent のクラスを付ける。
  3. .js-acccontent はcssで display:none; にする。
  4. 対応する js-accbtn要素 と js-acccontent要素 に
    同じ data-acc="値" を設定する。

最低限の設定はこれで終了です。

開閉時でのアコーディオンボタンのデザイン変更

アコーディオンオープン時と、クローズ時ではアコーディオンボタンのデザインを変える必要が出てくる場合がほとんどです。

ですので、アコーディオンが開いている時には、アコーディオンのボタン(js-accbtn)には open のクラスを付与させるようになっています。

こちら利用して、デザインを変えてください。

js-acccontentのレイアウト調整

htmlのコードには js-acccontent の中に inner 要素を入れています。

多くの場合、js-acccontent は padding や margin を使って、レイアウト調整をする場合があります。

しかし、js-acccontent に直接 padding や margin を付けると、開閉時の動作がカクついてしまいます。

そこで、内部のinner要素に padding や margin を付けて、レイアウトを調整してください。

1つのボタンで複数のコンテンツの開閉操作

今回のコードは、わざわざ 属性値 data-acc を使って、アコーディオンボタンとアコーディオンコンテンツを紐付けています。

これは、以前1つのアコーディオンボタンで、複数のコンテンツの開閉を操作するような仕様が必要になった場合があったからです。

そのような場合を想定して制作したので、今回のような仕様になっています。
(あまりない仕様な気がしますが。。。)

次回は、単に兄弟要素でのアコーディオンを実装したいと考えています。

javascriptのコード内容

ここからjavascriptの説明になってきますので、javascriptを改変して使用したい人は読んでみてください。

javascriptのコードを見ながら読みたい人はこちらをクリックして、別ウィンドウでコードを出してください。

2〜7行目

使う定数を定義しています。

2行目は、アコーディオンボタン要素のクラス名です。

3行目は、アコーディオンコンテンツ要素のクラス名です。

4行目は、アコーディオンオープン時にアコーディオンボタンにつくクラス名です。

5行目は、アコーディオンの開閉スピードです。

6行目は、アコーディオンが開閉するときに、分割してちょっとずつ開閉していくのですが、そのときの分割数です。

5行目と6行目を調整して、開閉時の動作を調整してください。

10行目: accClick 関数

アコーディオンボタン要素をクリックした時に発火します。

開閉どちらの動きをするかは、アコーディオンボタン要素に accOpenClass(4行目)のクラスがあるかどうかで、判断しています。

23行目: accOpen 関数

アコーディオンを開く時の関数。

24行目、最初にアコーディオンボタンに accOpenClass を付ける。

26行目、アコーディオンコンテンツの data-acc を取得して、アコーディオンボタンのdata-accと同じなら、28行目以降のアコーディオンコンテンツを開く動作へと移っていく。

29〜31 行目は、アコーディオンコンテンツの高さを取得するため、一度アコーディオンコンテンツを display:block; で表示させて、高さを取得してから、再び display:none; にしている。

32行目はアコーディオンの高さをコールバックで返している。

35・36行目、accOpenInterval 関数に渡す初期値。

37行目、分割して開く際の最小の高さ。取得した アコーディオンコンテンツの高さを accDivisor で割っている。

38〜40行目、アコーディオンコンテンツに開く直前のstyleを付けている。display:block;だが、heightが0の状態なので、現在は表示されていない状態。ここから、minHずつ、アコーディオンコンテンツが表示されるが、overflow:hidden;を付けていないと、いきなりすべて表示される。

47行目: accOpenInterval 関数

アコーディオンコンテンツがすべて表示されるまで、accDivisor回繰り返される関数。

48行目、accInterval(7行目)秒後のアコーディオンコンテンツ高さnextH。
nextHはminHずつ大きくなっていく。

49行目、countは accOpenInterval が何回繰り返されているかを判断するために用いている。
countがaccDivisorになったときに、accOpenIntervalの繰り返しが終わる。

52行目、countがaccDivisorに達していなかった場合は、再び accOpenInterval を呼び出す。
この時にすぐ呼び出しては、アコーディオンコンテンツが一瞬で表示されるので、accInterval 秒後に呼び出すようにsetTimeoutを用いている。

54行目、countがaccDivisorに達した場合、アコーディオンコンテンツのoverflow:hidden;を取る。

accClose関数、accCloseInterval関数

上記のaccOpen 関数とaccOpenInterval 関数の逆のことをやっているので、説明を割愛します。