初心者編|無料コーディング教材|HTML・CSS・jQueryを実践で学ぶ

jQueryでアコーディオンを作る方法|FAQのHTML・CSS・クリック処理を解説

jQueryでアコーディオンを作る記事のアイキャッチ

FAQやメニューをクリックで開閉する「アコーディオン」を作りたいけれど、HTML・CSS・jQueryをどう組み合わせればよいのか分からないことがありますよね。

jQueryでは、質問ボタンのクリックをon('click')で受け取り、回答をslideDown()slideUp()で開閉すると実装できます。

この記事では、そのまま試せるFAQアコーディオンをHTML、CSS、jQueryの順に作ります。スマートフォン表示やキーボード操作、1つだけ開く方法、動かない原因まで確認しましょう。

今回作るjQueryアコーディオン

最初はすべての回答を閉じ、質問をクリックすると対応する回答だけが開くFAQを作ります。閉じた状態は次のように表示されます。

すべての回答を閉じたFAQアコーディオンのブラウザ表示

質問を押すと回答が下へ開き、右端のプラス記号が閉じる記号へ変化します。

質問をクリックして回答を開いたFAQアコーディオンのブラウザ表示

質問部分にはbutton要素を使います。これにより、マウスだけでなくTabキーで移動し、Enterキーやスペースキーでも開閉できます。

アコーディオンのHTMLを作る

まず、index.htmlbody内へ質問と回答を記述します。1つの質問と回答をfaq-itemでまとめ、必要な数だけ繰り返してください。

<div class="faq">
  <section class="faq-item">
    <h2>
      <button
        class="faq-question"
        type="button"
        aria-expanded="false"
        aria-controls="faq-answer-1"
      >
        利用に料金はかかりますか?
        <span class="faq-icon" aria-hidden="true">+</span>
      </button>
    </h2>
    <div class="faq-answer" id="faq-answer-1" hidden>
      <p>基本機能は無料で利用できます。</p>
    </div>
  </section>

  <section class="faq-item">
    <h2>
      <button
        class="faq-question"
        type="button"
        aria-expanded="false"
        aria-controls="faq-answer-2"
      >
        スマートフォンでも使えますか?
        <span class="faq-icon" aria-hidden="true">+</span>
      </button>
    </h2>
    <div class="faq-answer" id="faq-answer-2" hidden>
      <p>はい。画面幅に合わせて見やすく表示されます。</p>
    </div>
  </section>
</div>

aria-controlsには、対応する回答のidを指定します。質問ごとに重複しない値を付けてください。

aria-expanded="false"は閉じている状態、trueは開いている状態を支援技術へ伝えます。回答のhidden属性は、初期状態で非表示にする指定です。

CSSでアコーディオンの見た目を整える

次に、style.cssへ質問と回答の装飾を追加します。質問ボタンを横幅いっぱいに広げ、開いているときはプラス記号を回転させます。

.faq {
  display: grid;
  gap: 14px;
}

.faq-item {
  overflow: hidden;
  border: 1px solid #cbd9e7;
  border-radius: 14px;
  background: #fff;
}

.faq-item h2 {
  margin: 0;
}

.faq-question {
  display: flex;
  width: 100%;
  align-items: center;
  gap: 14px;
  padding: 20px;
  border: 0;
  background: #fff;
  color: #203047;
  font: inherit;
  font-weight: 700;
  text-align: left;
  cursor: pointer;
}

.faq-question::before {
  content: 'Q';
  display: grid;
  flex: 0 0 34px;
  height: 34px;
  place-items: center;
  border-radius: 50%;
  background: #2474b5;
  color: #fff;
}

.faq-icon {
  margin-left: auto;
  color: #2474b5;
  font-size: 24px;
  transition: transform 0.2s;
}

.faq-question[aria-expanded='true'] .faq-icon {
  transform: rotate(45deg);
}

.faq-answer {
  padding: 0 22px 22px 68px;
  line-height: 1.8;
}

.faq-answer p {
  margin: 0;
  padding-top: 18px;
  border-top: 1px solid #d8e3ed;
}

aria-expanded='true'をCSSの条件に使うため、jQueryが状態を更新すると見た目も連動します。状態を表すclassを別に管理する必要がありません。

スマートフォンの余白を調整する

画面幅が狭い場合は、質問と回答の左右余白を小さくします。先ほどのstyle.cssの末尾へ追加してください。

@media (max-width: 560px) {
  .faq-question {
    padding: 16px;
  }

  .faq-answer {
    padding: 0 16px 18px;
  }
}

jQueryでクリック時に回答を開閉する

最後に、jQuery本体を読み込んだ後のscript.jsへ開閉処理を記述します。

jQuery(function ($) {
  $('.faq-answer[hidden]').removeAttr('hidden').hide();

  $('.faq-question').on('click', function () {
    const $button = $(this);
    const answerId = $button.attr('aria-controls');
    const $answer = $('#' + answerId);
    const willOpen = $button.attr('aria-expanded') === 'false';

    $button.attr('aria-expanded', String(willOpen));

    if (willOpen) {
      $answer.removeAttr('hidden').slideDown(200);
    } else {
      $answer.slideUp(200, function () {
        $answer.attr('hidden', '');
      });
    }
  });
});

最初の行は、hiddenが付いた回答をjQueryのアニメーションで扱える状態にしてから非表示にしています。

クリック時はaria-controlsから対応する回答を取得し、現在のaria-expandedを見て開くか閉じるかを判断します。slideDown(200)slideUp(200)の200は、アニメーション時間をミリ秒で指定した値です。

1つ開いたら他の回答を閉じる

常に1つだけ開く形式にしたい場合は、新しい回答を開く前に、開いているほかの回答を閉じます。先ほどのクリック処理を次の内容へ変更してください。

jQuery(function ($) {
  $('.faq-answer[hidden]').removeAttr('hidden').hide();

  $('.faq-question').on('click', function () {
    const $button = $(this);
    const $answer = $('#' + $button.attr('aria-controls'));
    const willOpen = $button.attr('aria-expanded') === 'false';

    $('.faq-question[aria-expanded="true"]')
      .not($button)
      .attr('aria-expanded', 'false');

    $('.faq-answer:visible')
      .not($answer)
      .slideUp(200, function () {
        $(this).attr('hidden', '');
      });

    $button.attr('aria-expanded', String(willOpen));

    if (willOpen) {
      $answer.removeAttr('hidden').slideDown(200);
    } else {
      $answer.slideUp(200, function () {
        $answer.attr('hidden', '');
      });
    }
  });
});

.not($button).not($answer)により、いま操作した質問と回答を閉じる対象から除外しています。

最初から1つ開いて表示する

最初の回答を開いた状態にする場合は、その質問をaria-expanded="true"にし、回答からhiddenを外します。

<button
  class="faq-question"
  type="button"
  aria-expanded="true"
  aria-controls="faq-answer-1"
>
  利用に料金はかかりますか?
  <span class="faq-icon" aria-hidden="true">+</span>
</button>

<div class="faq-answer" id="faq-answer-1">
  <p>基本機能は無料で利用できます。</p>
</div>

初期状態のHTMLとaria-expandedを一致させることが大切です。見た目だけ開いているのに値がfalseだと、支援技術へ誤った状態が伝わります。

アコーディオンが動かないときの確認ポイント

  • jQuery本体より後にscript.jsを読み込んでいるか
  • aria-controlsと回答のidが同じか
  • 回答ごとのidが重複していないか
  • script.jsをHTMLの読み込み前に実行していないか
  • 別のCSSでdisplay: block !importantを指定していないか

表示・非表示を切り替えるjQueryメソッドの違いは、次の記事でtoggle()slideToggle()の例とともに解説しています。

クリックイベントと動的要素への設定方法を深く知りたい場合は、次の記事も参考にしてください。

まとめ

  • 質問にはキーボードでも操作できるbutton要素を使う
  • aria-controlsで質問と回答を対応させる
  • aria-expandedを開閉状態に合わせて更新する
  • slideDown()slideUp()で回答をアニメーション表示する
  • スマートフォンでは左右の余白を調整する

まずは2つの質問で基本コードを動かし、問題なく開閉できたらFAQの数や色、開閉ルールを調整してみてください。