素のJavaScriptでアニメーション付きアコーディオンを実装する方法【3通り】

Webサイト上では、アコーディオンメニューがよく使われていますよね。

コンテンツを折り畳むことができるため、

  • ユーザーが欲しい情報をピンポイントで探せる
  • ページが縦長になりすぎない
  • 少ないスペースで多くの情報を収納できる

といったようなメリットがあります。

このアコーディオンメニューの多くはjQueryのスライド系メソッドで実装されていますね。

素のJavaScriptで実装したいと思い調べてみると、以下のような問題点があるものばかり紹介されてました。

  • paddingを指定すると崩れる
  • 文章が複数行になると、閉じる時に潰れたような見た目になる
  • 開閉のアニメーションが不自然

これならばjQueryを使った方がましですが、アコーディオンの実装だけのためにjQueryを読み込みたくはないですよね…。

そこで今回の記事では、jQueryのslide系メソッドと全く同じ挙動のアコーディオンメニューを素のJavaScriptで実装する方法を紹介します!

DEMOや実装コードも紹介していますので、JavaScriptでアコーディオンを実装したい方はぜひ参考にしてみてください。

目次

アコーディオンの3種類のDEMOと実装コード

今回は3つのアコーディオンを用意しました。

  1. 通常バージョン
  2. 最初の一つを開けておくバージョン
  3. 一度に一つだけ開くバージョン

次でそれぞれのDEMOと実装コードを紹介していきます!

コードをコピペするだけで動作するので、自由にお使いください。

DEMO1. 通常バージョン

まずは一番シンプルなアコーディオンから見ていきましょう。

要素をクリックしてみてください。

DEMO1のアコーディオンタイトル
ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
DEMO1のアコーディオンタイトル
ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
DEMO1のアコーディオンタイトル
ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。

DEMO1の実装コード

\ クリックでタブを切り替えられます /

<dl class="accordion js-accordion">
  <div class="accordion__item js-accordion-trigger">
    <dt class="accordion__title">DEMO1のタイトル</dt>
    <dd class="accordion__content">DEMO1のコンテンツ</dd>
  </div>
  <div class="accordion__item js-accordion-trigger">
    <dt class="accordion__title">DEMO1のタイトル</dt>
    <dd class="accordion__content">DEMO1のコンテンツ</dd>
  </div>
  <div class="accordion__item js-accordion-trigger">
    <dt class="accordion__title">DEMO1のタイトル</dt>
    <dd class="accordion__content">DEMO1のコンテンツ</dd>
  </div>
</dl>

DEMO2. 最初の一つを開けておくバージョン

ユーザーに開閉可能であることを示すために、最初のひとつだけ最初から開いておくバージョンです。

DEMO2のアコーディオンタイトル
ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
DEMO2のアコーディオンタイトル
ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
DEMO2のアコーディオンタイトル
ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。

DEMO2の実装コード

\ クリックでタブを切り替えられます /

<dl class="accordion js-accordion">
  <div class="accordion__item js-accordion-trigger is-active">
    <dt class="accordion__title">DEMO2のタイトル</dt>
    <dd class="accordion__content is-open">DEMO2のコンテンツ</dd>
  </div>
  <div class="accordion__item js-accordion-trigger">
    <dt class="accordion__title">DEMO2のタイトル</dt>
    <dd class="accordion__content">DEMO2のコンテンツ</dd>
  </div>
  <div class="accordion__item js-accordion-trigger">
    <dt class="accordion__title">DEMO2のタイトル</dt>
    <dd class="accordion__content">DEMO2のコンテンツ</dd>
  </div>
</dl>

DEMO3. 一度に一つだけ開くバージョン

一度にひとつだけ開くバージョンです。

二つ以上開こうとすると、最初のアコーディオンが閉じる仕組みになっています。

DEMO3のアコーディオンタイトル
ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
DEMO3のアコーディオンタイトル
ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
DEMO3のアコーディオンタイトル
ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。

DEMO3の実装コード

\ クリックでタブを切り替えられます /

<dl class="accordion js-accordion">
  <div class="accordion__item js-accordion-trigger">
    <dt class="accordion__title">DEMO3のタイトル</dt>
    <dd class="accordion__content">DEMO3のコンテンツ</dd>
  </div>
  <div class="accordion__item js-accordion-trigger">
    <dt class="accordion__title">DEMO3のタイトル</dt>
    <dd class="accordion__content">DEMO3のコンテンツ</dd>
  </div>
  <div class="accordion__item js-accordion-trigger">
    <dt class="accordion__title">DEMO3のタイトル</dt>
    <dd class="accordion__content">DEMO3のコンテンツ</dd>
  </div>
</dl>

アコーディオンの実装コードをそれぞれ解説【HTML・CSS・JavaScript】

ここからは、実装コードの解説をしていきます。

上記の順で解説していきますね。

HTML

まずはHTMLからみていきましょう。

<dl class="accordion js-accordion">
  <div class="accordion__item js-accordion-trigger">
    <dt class="accordion__title">DEMOのタイトル</dt>
    <dd class="accordion__content">DEMOのコンテンツ</dd>
  </div>
  <div class="accordion__item js-accordion-trigger">
    <dt class="accordion__title">DEMOのタイトル</dt>
    <dd class="accordion__content">DEMOのコンテンツ</dd>
  </div>
  <div class="accordion__item js-accordion-trigger">
    <dt class="accordion__title">DEMOのタイトル</dt>
    <dd class="accordion__content">DEMOのコンテンツ</dd>
  </div>
</dl>

アコーディオンの実装には、説明リストタグdldtddを使用します。

dlタグでアコーディオン全体を囲い、JavaScriptで指定するためにjs-accordionのクラスを付与します。

dlタグの中には、dtddのセットを囲んだdivタグを配置します。

ちなみによく勘違いされますが、dlの中にdivを配置するのはOKです。
(参考: 説明リスト要素 – MDN)

このdivをクリックした際にコンテンツを展開or閉じたいので、js-accordion-triggerクラスを指定しておきます。

あとはdtにタイトル、ddに展開/収縮させるコンテンツを入れて完成です!

最初からアコーディオンを開けておきたい場合はクラスを追加

DEMO2で紹介したように、最初からアコーディオンを展開しておきたい場合は、以下の2つのクラスを追加します。

<dl class="accordion js-accordion">
  <div class="accordion__item js-accordion-trigger is-active">
    <dt class="accordion__title">DEMOのタイトル</dt>
    <dd class="accordion__content is-open">DEMOのコンテンツ</dd>
  </div>
  <div class="accordion__item js-accordion-trigger">
    <dt class="accordion__title">DEMOのタイトル</dt>
    <dd class="accordion__content">DEMOのコンテンツ</dd>
  </div>
  <div class="accordion__item js-accordion-trigger">
    <dt class="accordion__title">DEMOのタイトル</dt>
    <dd class="accordion__content">DEMOのコンテンツ</dd>
  </div>
</dl>
  1. accordion__itemis-activeを追加
  2. accordion__contentis-openを追加

これではじめから要素が展開された状態になりました。

DEMOでは最初の一つのみ展開していましたが、複数展開することも可能です。

CSS

続いてはCSSの解説です。

まずは全体のCSSを確認してみましょう。

/* 簡易リセットCSS */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* アコーディオン全体 */
.accordion {
  max-width: 800px;
  margin: 0 auto;
}

/* アコーディオン */
.accordion__item {
  border: 1px solid #ccc;
  margin-top: 10px;
  cursor: pointer;
}

/* アコーディオンのタイトル */
.accordion__title {
  position: relative;
  padding: 15px 60px 15px 20px;
  font-weight: bold;
  cursor: pointer;
}

/* (+)アイコン */
.accordion__title::before,
.accordion__title::after {
  content: "";
  position: absolute;
  right: 20px;
  top: 0;
  bottom: 0;
  margin: auto 0;
  background-color: #3abec1;
  width: 20px;
  height: 4px;
  transition: all 0.3s;
}

.accordion__title::after {
  transform: rotate(90deg);
}

/* アコーディオンのコンテンツ */
.accordion__content {
  padding: 0 20px 15px 20px;
  display: none;
  cursor: pointer;
}

/* アコーディオンのコンテンツ展開時 */
.accordion__content.is-open {
  display: block;
}

/* アコーディオン展開時の(-)アイコン */
.accordion__item.is-active .accordion__title::before {
  transform: rotate(180deg);
}

.accordion__item.is-active .accordion__title::after {
  transform: rotate(180deg);
  opacity: 0;
}

CSSのほとんどは見た目の調整なので、アコーディオンの機能に関わる重要な部分のみ説明します。

/* アコーディオン収縮時 */
.accordion__content {
  display: none;
}

/* アコーディオン展開時 */
.accordion__content.is-open {
  display: block;
}

まずアコーディオンのコンテンツ部分にdisplay: noneを指定します。

これで初期状態ではコンテンツが隠れるようになりました。

クリックするとコンテンツを展開したいので、is-openクラスがついている時にdisplay: blockを指定します。

このis-openクラスはJavaScriptで付与/削除するので、ここでは考える必要はありません。

DEMOでは値にblockを指定していますが、flexinline-block等も指定することができます。

この部分以外は全て見た目の調整なので、自由にカスタマイズしてください。

これでCSSの説明は終わりです。

JavaScript

最後にアコーディオンの機能部分であるJavaScriptの解説をしていきます。

JavaScriptのコードは、大きく以下の2つに分かれます。

  • slideUp/slideDown/slideToggle関数の定義
  • DOM操作

それぞれ別々に説明していきますね。

JSパート1. slideUp, slideDown, slideToggle関数を定義

まず要素を滑らかに表示/非表示にするための関数を定義していきます。

jQueryを使ったことがある方はわかると思いますが、slideUp, slideDown, slideToggleという便利なメソッドがありますね。

これらを素のJavaScriptで再現したものが以下のコードになります。

// 要素をスライドしながら非表示にする関数(jQueryのslideUpと同じ)
const slideUp = (el, duration = 300) => {
  el.style.height = el.offsetHeight + "px";
  el.offsetHeight;
  el.style.transitionProperty = "height, margin, padding";
  el.style.transitionDuration = duration + "ms";
  el.style.transitionTimingFunction = "ease";
  el.style.overflow = "hidden";
  el.style.height = 0;
  el.style.paddingTop = 0;
  el.style.paddingBottom = 0;
  el.style.marginTop = 0;
  el.style.marginBottom = 0;
  setTimeout(() => {
    el.style.display = "none";
    el.style.removeProperty("height");
    el.style.removeProperty("padding-top");
    el.style.removeProperty("padding-bottom");
    el.style.removeProperty("margin-top");
    el.style.removeProperty("margin-bottom");
    el.style.removeProperty("overflow");
    el.style.removeProperty("transition-duration");
    el.style.removeProperty("transition-property");
    el.style.removeProperty("transition-timing-function");
    el.classList.remove("is-open");
  }, duration);
};

// 要素をスライドしながら表示する関数(jQueryのslideDownと同じ)
const slideDown = (el, duration = 300) => {
  el.classList.add("is-open");
  el.style.removeProperty("display");
  let display = window.getComputedStyle(el).display;
  if (display === "none") {
    display = "block";
  }
  el.style.display = display;
  let height = el.offsetHeight;
  el.style.overflow = "hidden";
  el.style.height = 0;
  el.style.paddingTop = 0;
  el.style.paddingBottom = 0;
  el.style.marginTop = 0;
  el.style.marginBottom = 0;
  el.offsetHeight;
  el.style.transitionProperty = "height, margin, padding";
  el.style.transitionDuration = duration + "ms";
  el.style.transitionTimingFunction = "ease";
  el.style.height = height + "px";
  el.style.removeProperty("padding-top");
  el.style.removeProperty("padding-bottom");
  el.style.removeProperty("margin-top");
  el.style.removeProperty("margin-bottom");
  setTimeout(() => {
    el.style.removeProperty("height");
    el.style.removeProperty("overflow");
    el.style.removeProperty("transition-duration");
    el.style.removeProperty("transition-property");
    el.style.removeProperty("transition-timing-function");
  }, duration);
};

// 要素をスライドしながら交互に表示/非表示にする関数(jQueryのslideToggleと同じ)
const slideToggle = (el, duration = 300) => {
  if (window.getComputedStyle(el).display === "none") {
    return slideDown(el, duration);
  } else {
    return slideUp(el, duration);
  }
};

これらを簡単に説明すると、

  • slideUp…要素を非表示 & 要素からis-openクラスを除去
  • slideDown…要素を表示 & 要素にis-openクラスを付与
  • slideToggle…要素が非表示の場合はslideDown、表示されている場合はslideUpを返す

という機能になります。

これに関しては全て説明すると長くなるので、解説記事を書きました。

理解を深めたい方はぜひ以下の記事に目を通してみてください。

JSパート2. DOM操作

ここではパート1で定義した関数を使って、アコーディオンを実装していきます。

DEMO1, DEMO2は共通のコードを使用しますが、DEMO3は少し異なるので、順番に説明していきます。

DEMO1 & DEMO2のJavaScriptコード
// アコーディオンを全て取得
const accordions = document.querySelectorAll(".js-accordion");
// 取得したアコーディオンをArrayに変換(IE対策)
const accordionsArr = Array.prototype.slice.call(accordions);

accordionsArr.forEach((accordion) => {
  // Triggerを全て取得
  const accordionTriggers = accordion.querySelectorAll(".js-accordion-trigger");
  // TriggerをArrayに変換(IE対策)
  const accordionTriggersArr = Array.prototype.slice.call(accordionTriggers);

  accordionTriggersArr.forEach((trigger) => {
    // Triggerにクリックイベントを付与
    trigger.addEventListener("click", () => {
      // '.is-active'クラスを付与or削除
      trigger.classList.toggle("is-active");
      // 開閉させる要素を取得
      const content = trigger.querySelector(".accordion__content");
      // 要素を展開or閉じる
      slideToggle(content);
    });
  });
});

こちらがDEMO1とDEMO2共通のコードです。

まずはもう一度実装DEMOを確認してみましょう。

DEMO1のアコーディオン
DEMO1のアコーディオンタイトル
ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
DEMO1のアコーディオンタイトル
ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
DEMO1のアコーディオンタイトル
ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
DEMO2のアコーディオン
DEMO2のアコーディオンタイトル
ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
DEMO2のアコーディオンタイトル
ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
DEMO2のアコーディオンタイトル
ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。

DEMOが確認できたので、コードの解説をしていきます。

1~4行目
// アコーディオンを全て取得
const accordions = document.querySelectorAll(".js-accordion");
// 取得したアコーディオンをArrayに変換(IE対策)
const accordionsArr = Array.prototype.slice.call(accordions);

まずはアコーディオンをJavaScriptで操作するために、querySelectorAllで全て取得します。

次に取得したデータの型をNodeListからArrayに変換します。

IEなどの古いブラウザでは、NodeListに対してforEachメソッドが使用できないため、その対策ですね。

6行目
accordionsArr.forEach((accordion) => {
  // 中身は省略
});

1~4行目で全てのアコーディオンを取得できたので、個々のアコーディオンに対して処理をするためにforEachメソッドを使用します。

7~10行目
// Triggerを全て取得
const accordionTriggers = accordion.querySelectorAll(".js-accordion-trigger");
// TriggerをArrayに変換(IE対策)
const accordionTriggersArr = Array.prototype.slice.call(accordionTriggers);

HTMLパートで、クリックする要素に対してjs-accordion-triggerクラスを持つdivを作成しましたね。

これらをquerySelectorAllで全て取得します。

また、次でforEachを使うので、先ほどと同様にNodeListからArrayに変換します。

12行目
accordionTriggersArr.forEach((trigger) => {
  // 個々のTriggerに対しての処理
});

Arrayに変換したTriggerに対して個別に処理をするために、forEachメソッドを使用します。

13~21行目
// Triggerにクリックイベントを付与
trigger.addEventListener("click", () => {
  // '.is-active'クラスを付与or削除
  trigger.classList.toggle("is-active");
  // 開閉させる要素を取得
  const content = trigger.querySelector(".accordion__content");
  // 要素を展開or閉じる
  slideToggle(content);
});

ここでは個々のTriggerに対して、addEventListenerでクリックイベントを登録します。

Triggerをクリックすると、以下の二つの処理が行われます。

  • Triggerにis-activeクラスを付与するor取り除く
  • コンテンツを表示or非表示にする

まずクラスの付け外しですが、classList.toggleを使用してis-activeクラスがあるなら取り除く、ないなら付与することができます。

次にコンテンツの表示/非表示に関しては、最初に準備したslideToggle関数を使用します。

この関数では、

  • 要素が表示されている場合…非表示にする
  • 要素が非表示の場合…表示する

という風に動作しますね。

これでクリックするたびにアコーディオンが開閉するようになりました!

DEMO3のJavaScriptコード

// アコーディオンを全て取得
const accordions = document.querySelectorAll(".js-accordion");
// 取得したアコーディオンをArrayに変換(IE対策)
const accordionsArr = Array.prototype.slice.call(accordions);

accordionsArr.forEach((accordion) => {
  // Triggerを全て取得
  const accordionTriggers = accordion.querySelectorAll(".js-accordion-trigger");
  // TriggerをArrayに変換(IE対策)
  const accordionTriggersArr = Array.prototype.slice.call(accordionTriggers);

  accordionTriggersArr.forEach((trigger) => {
    // Triggerにクリックイベントを付与
    trigger.addEventListener("click", (e) => {
      accordionTriggersArr.forEach((trigger) => {
        // クリックしたアコーディオン以外を全て閉じる
        if (trigger !== e.target.parentElement) {
          trigger.classList.remove("is-active");
          const openedContent = trigger.querySelector(".accordion__content");
          slideUp(openedContent);
        }
      });

      // '.is-active'クラスを付与or削除
      trigger.classList.toggle("is-active");
      // 開閉させる要素を取得
      const content = trigger.querySelector(".accordion__content");
      // 要素を展開or閉じる
      slideToggle(content);
    });
  });
});

こちらがDEMO3の実装コードです。

DEMO3のアコーディオンタイトル
ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
DEMO3のアコーディオンタイトル
ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。
DEMO3のアコーディオンタイトル
ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。ここにアコーディオンのコンテンツが入ります。

DEMO3では、一度に一つだけ開ける仕様になっていますね。

DEMO1・DEMO2と共通のコードがほとんどなので、違いがある部分のみ解説していきます!

14行目
trigger.addEventListener("click", (e) => {
  // 個々のTriggerに対しての処理
});

先ほどとの違いは、関数の第一引数にeventオブジェクトを渡しているところです。

このイベントオブジェクトeを通して、クリックした要素に関する情報を得ることができます。

15~22行目
accordionTriggersArr.forEach((trigger) => {
  // クリックしたアコーディオン以外を全て閉じる
  if (trigger !== e.target.parentElement) {
    trigger.classList.remove("is-active");
    const openedContent = trigger.querySelector(".accordion__content");
    slideUp(openedContent);
  }
});

ここでは、クリックしたアコーディオン以外を閉じるという処理を行なっています。

これを実装するには、まずクリックしたTriggerとそれ以外のTriggerを判別する必要がありますね。

まずe.targetで、クリックした要素を取得します。

さらにparentElementプロパティを使うことで、その親要素であるTriggerを取得できます。

これで、クリックしたTriggerを判別することができました。

if文の条件式では、“クリックしたTrigger以外の全てのTriggerに括弧内のコードを実行する”という指定をします。

if文の括弧内の指示は、以下のとおりです。

  • Triggerからis-activeクラスを取り除く
  • slideUp関数を呼び出し、既に開いているコンテンツを閉じる

これで、開いているアコーディオンがある場合はまず閉じるという動作を実装することができました。

あとはDEMO1のコードと同じで、クリックしたアコーディオンを開閉する処理を書き入れて完成です。

【まとめ】jQueryなしでアニメーション付きアコーディオンを実装する方法

JavaScriptでアニメーション付きアコーディオンを実装する方法を紹介しました。

素のJSでアコーディオンを実装する方法はいくつか紹介されていますが、その多くはアニメーションがぎこちなかったり、表示が崩れるなど問題点が多いです…。

この記事ではjQueryのスライド系メソッドをJavaScriptに変換して使用しているので、綺麗なアニメーションのアコーディオンを実装することが可能です!

アコーディオンはWeb制作の案件でもよく使う機能なので、ぜひこの記事で紹介したコードを使ってみてください。

今回使用したslide系関数については以下の記事で解説していますので、さらに理解を深めたい方は以下のリンクからどうぞ。

Web制作に関する
記事案を募集中!

Web制作について知りたいこと、質問等ありましたら、以下のフォームから気軽に投稿してください。
要望が多かったものは解説記事を作成します。

よかったらシェアしてね!

コメント

コメントする

目次
閉じる