今回はJavaScriptを使って
クリックで要素の表示・非表示を切り替える

通称「アコーディオン」を作っていきます。

まずは完成形はコチラ

See the Pen Untitled by subaru (@subaru_works) on CodePen.

「ここをクリック!」と書かれた部分をクリックすると、
その下に要素がニュッとアニメーションで表示されます。

もう一度クリックすると、再度アニメーションで要素が非表示になります。

HTML

<div class="acc-handle">
        …クリックする要素...
</div>
<div class="acc-body">
        …アニメーションする要素...
</div>

HTMLはクリックする要素の .acc-handle
アニメーションする要素の .acc-body を兄弟要素として作成します

それぞれの要素の中身は基本的自由に設定してOKです。

ここで重要なのでは、クリックする要素とアニメーションする要素を隣同士の要素にすることです。

CSS

.acc-handle {
  width: 600px;
  padding: 1rem;
  cursor: pointer;
  * {
    pointer-events: none;
  }
}

.acc-body {
  display: none;
  width: 600px;
  overflow: hidden;
}

こちらは最低限のCSSを抜き出したものになります。

クリックする要素の .acc-handle は、横幅の指定とカーソルの指定
そして、子要素に様々な要素が追加されることを見越して
クリックイベントを制御する pointer-events: none; を設定しています。

これらは無くても動作はします。

アニメーションする要素の .acc-body は、初期表示は「非表示」にしたいので
display: none; としておきます。
また、要素の中身が枠外に表示されないように overflow: hidden; もかけておきます。

JavaScript

いちばん重要なのはJavaScriptです。

const accHandle = document.querySelector(".acc-handle");

accHandle.addEventListener("click", (event) => {
  const accbody = event.target.nextElementSibling;
  event.target.classList.toggle('is-open');
  slideToggle(accbody, 500);
})

まずはクリック時の挙動です。

.acc-handle にイベントリスナーを登録しています。

4行目では、クリックされた要素の兄弟要素を取得して
accbody としておきます。

次の行では、クリックされた要素に is-open のクラスを追加
これはCSSで見た目を変更するために追加しています。

そして、6行目で slideToggle() という別の関数を呼び出しています。
実際のアニメーションはこの関数内で行われています。

アニメーションを実行している関数

アニメーションを実行している関数の中身はこちらです

// slideUp
function slideUp(target, duration = 500) {
  target.style.transitionProperty = 'height';
  target.style.transitionDuration = duration + 'ms';
  target.style.height = `${target.offsetHeight}px`;
  
  target.offsetHeight;
  target.style.height = '0';
  window.setTimeout(() => {
    target.style.display = 'none';
    // スタイルをクリア
    ['height', 'transition-duration','transition-property']
    .forEach(prop => target.style.removeProperty(prop));
  }, duration);
}

まずは、「閉じる」アニメーションです。

jsで変更させる要素の height target.offsetHeight で取得しておき
setTimeout を使ってアニメーションを時差で発生させます。

実行しているのは CSSの transition でのアニメーションと同じですが、
発火タイミングをずらすことでCSSだけでは高さを取れない問題を回避しています。

お次は「開く」アニメーション

// slideDown
function slideDown(target, duration = 500) {
  target.style.removeProperty('display');
  let display = getComputedStyle(target).display;
  if (display === 'none') display = 'block';
  target.style.display = display;

  const height = target.offsetHeight;
  target.style.height = '0';

  target.offsetHeight;
  target.style.transitionProperty = 'height';
  target.style.transitionDuration = `${duration}ms`;
  target.style.height = `${height}px`;

  window.setTimeout(() => {
    ['height', 'transition-duration','transition-property']
    .forEach(prop => target.style.removeProperty(prop));
  }, duration);
}

全体の流れは先程の閉じる動作と同じです。

要素の高さを取得してから時差で transition を起こして
高さアニメーションが発火するようにしています

開く、閉じるの挙動を作ることができました

クリックごとに呼び出す「開く」と「閉じる」を判定して
呼び出す関数を自動で変えるように「トグル」の設定を行います。

// slideToggle
function slideToggle(target, duration = 500) {
  if (getComputedStyle(target).display === 'none') {
    slideDown(target, duration);
  } else {
    slideUp(target, duration);
  }
}

要素が「非表示」の状態であれば slideDown() を呼んで要素を開きます。
そうでない場合は slideUp() を呼んで、要素を閉じます。

完成

以上がJavaScriptで「アコーディオン」を作るほう法でした!

実は、こんなにややこしくJSのコードを書かなくても実行できる方法があります
それは…

jQueryを使うこと!

jQueryを読み込んでいれば、jQueryであらかじめ設定されている
slideUp()slideDown()slideToggle() で簡単に指定すことができます。

じゃあjQuery使えばいいじゃない?

と思いますよね。

プロジェクト内でjQueryを利用する要素が多い場合や
メンテナンスの頻度が低い場合は
jQueryを読み込んで利用するのでもアリだと思います

しかしjQueryは便利な半面、ライブラリ自体が重くなる場合があります。
これはページ表示時にもたつく原因になる場合もります。

また、jQueryのバージョンの管理が大変になる場合があるため
近年ではjQueryを使わない場面のほうが多くなってきました。

プロジェクトの性質に合わせてjQuery を利用するのか
JavaScriptで実装するのか…選べるようになると便利かと思います。

まとめ

というわけで、今回はJavaScriptでアコーディオンを実装する方法について紹介しました。