こんにちは、かつコーチです。
以前、JavaScript基礎編の記事で、素のJavaScriptを使ったアコーディオン(FAQ)の開閉実装を紹介しました。
今回はここからが「実践UI実装」シリーズの1本目です。
同じアコーディオンをjQueryで実装するとどう変わるかを、コード量や書き方の違いを軸に比較していきます。
レガシー案件の保守で「素のJSなら書けるけど、jQueryのコードが読めない」という方にも役立つ内容にしています。
基本の実装手順
手順1:HTMLはそのまま流用できる
HTML構造は、素のJS版と同じもので問題ありません。
<div class="faq">
<div class="faq__item">
<button class="faq__question" aria-expanded="false">
料金プランは途中で変更できますか?
</button>
<div class="faq__answer">
<p>はい、いつでも上位・下位プランへの変更が可能です。マイページの「プラン変更」から手続きできます。</p>
</div>
</div>
<div class="faq__item">
<button class="faq__question" aria-expanded="false">
無料トライアルはありますか?
</button>
<div class="faq__answer">
<p>14日間の無料トライアルをご用意しています。クレジットカードの登録は不要です。</p>
</div>
</div>
</div>
jQueryを導入したからといって、マークアップを変える必要はありません。
変わるのは、あくまでJavaScript(jQuery)側の書き方です。
手順2:slideToggle()で開閉する
素のJS版では、scrollHeightを取得してmax-heightに代入する、というひと手間が必要でした。
jQueryにはslideToggle()(要素の高さを0から自然な高さまでアニメーションさせながら表示・非表示を切り替えるメソッド)が最初から用意されています。
$('.faq__question').on('click', function () {
const $answer = $(this).next('.faq__answer');
$(this).toggleClass('is-open');
$answer.slideToggle(300);
});
next()で直後の要素(回答部分)を取得し、slideToggle(300)で300ミリ秒かけて開閉させています。
CSS側でmax-heightのトランジションを書く必要も、JavaScript側でscrollHeightを計算する必要もありません。
手順3:CSSはシンプルになる
素のJS版で書いていたmax-heightやtransitionの指定は不要になります。
.faq__item {
border-bottom: 1px solid #ddd;
}
.faq__question {
width: 100%;
padding: 16px;
text-align: left;
background: #fff;
border: none;
font-size: 16px;
font-weight: bold;
cursor: pointer;
}
.faq__answer {
display: none;
background: #fafafa;
}
.faq__answer p {
margin: 0;
padding: 16px;
}
slideToggle()がdisplayと高さのアニメーションを両方まとめて面倒を見てくれるため、初期状態はdisplay: none;を指定しておくだけで済みます。
素のJS版との比較
コード量・可読性の比較表
同じ「単独展開なしの単純な開閉」を実装した場合の比較です。
| 項目 | 素のJavaScript版 | jQuery版 |
|---|---|---|
| 要素取得の行数 | querySelectorAll+forEachで2行 | $('.faq__question')の1行 |
| 開閉処理の行数 | 高さ計算を含め約8行 | slideToggle()の1行 |
| CSSの記述量 | max-heightとtransitionが必要 | display: none;のみで十分 |
| アニメーションの滑らかさ | scrollHeight頼みで実装者が調整 | ライブラリが吸収してくれる |
| 依存ライブラリ | 不要 | jQueryの読み込みが必要 |
| 学習面でのメリット | ブラウザAPIの仕組みが身につく | 実装速度が上がる |
こうして並べると、jQuery版は開閉処理そのものの記述量が圧倒的に少ないことが分かります。
scrollHeightを自分で計算する必要がなく、slideToggle()という1つのメソッドにアニメーションの面倒な部分を任せられるためです。
一方で、素のJS版は「高さをどう扱うか」というブラウザAPIの仕組みそのものを学べる点にメリットがあります。
つまずきやすいポイント:単独展開にすると全部閉じてしまう
私が実際にハマった経験
私が最初にjQuery版のアコーディオンで「1つ開いたら他は閉じる」単独展開を実装したとき、クリックした項目自体もなぜか閉じてしまう不具合にハマりました。
「クリックした項目を開く」処理の前に「全部閉じる」処理を書いていたのが原因で、開いたそばから自分自身も閉じられていた、という単純なミスでした。
❌ Before:先に自分を開いてから全部閉じてしまっている
$('.faq__question').on('click', function () {
$(this).next('.faq__answer').slideDown(300);
// 先に自分を開いたつもりが…
$('.faq__answer').not($(this).next()).slideUp(300);
$('.faq__answer').slideUp(300); // ここで全部閉じてしまう
});
似たような処理を重ねて書いてしまい、後から書いた「全部閉じる」処理が自分自身にもかかってしまうミスです。
✅ After:開閉状態を判定してから一括で処理する
$('.faq__question').on('click', function () {
const $answer = $(this).next('.faq__answer');
const isOpen = $answer.is(':visible');
// 先に全項目を閉じる
$('.faq__answer').slideUp(300);
$('.faq__question').removeClass('is-open');
// クリックした項目が「閉じていた」場合のみ開き直す
if (!isOpen) {
$answer.slideDown(300);
$(this).addClass('is-open');
}
});
:is(':visible')で現在の開閉状態を先に判定しておき、「全部閉じる → 元が閉じていた場合だけ開き直す」という順序にすることで、トグル動作が正しく成立します。
これは素のJS版の単独展開実装でも同じ考え方を使っており、「先に状態を確認してから一括処理する」という発想はライブラリが変わっても共通するポイントです。
応用・一歩先の使い方
アニメーションの速度・イージングを調整する
slideToggle()は第1引数にミリ秒、第2引数にイージング(変化の緩急)を指定できます。
$answer.slideToggle(400, 'swing');
初期状態ではswingとlinearの2種類しか使えませんが、jquery.easingプラグインを追加すると、より豊富なイージングが使えるようになります。
コールバックで開閉後の処理を挟む
slideToggle()の第3引数(またはオブジェクトのcomplete)に関数を渡すと、アニメーション完了後の処理を追加できます。
$answer.slideToggle(300, function () {
console.log('アニメーションが完了しました');
});
高さが変わった後にレイアウトを再計算したい場合など、完了タイミングを厳密に扱いたいときに便利です。
まとめ
この記事のポイント
- jQueryの
slideToggle()を使うと、scrollHeightの計算なしで開閉アニメーションが実装できる - 素のJS版と比べて開閉処理のコード量が大幅に減る一方、CSSのアニメーション制御はjQuery任せになる
- 単独展開を実装するときは、「全部閉じる → 元が閉じていた場合だけ開き直す」という順序を守る
- アニメーションの速度やコールバックは
slideToggle()の引数で細かく調整できる
次に読むべき記事
次回は、タブ切り替えをjQueryで実装しながら、siblings()を使ったアクティブ状態の管理方法を解説します。
素のJavaScript版でのアコーディオン実装は、JavaScript基礎編の「アコーディオン(FAQ)の開閉を実装する」で紹介しています。
書き方の違いをあわせて確認しておくと、どちらの現場に入っても対応しやすくなります。