9/7まで 最大30%OFF 初心者編|無料コーディング教材|HTML・CSS・jQueryを実践で学ぶ

jQueryのtoggle()で表示・非表示を切り替える方法|FAQ実例も初心者向けに解説

jQuery toggleの使い方を示すアイキャッチ

jQueryでボタンを押すたびに文章を表示・非表示へ切り替えるには、toggle()を使います。FAQの回答や補足パネルを、短いコードで開閉できます。

ただし、表示を切り替えるだけでは、キーボードや読み上げソフトを使う人に開閉状態が伝わりません。実際の制作ではbutton要素を使い、aria-expandedも一緒に更新します。

この記事では、toggle()の基本、速度の指定、slideToggle()との違い、アクセシブルなFAQ、連打時や動かないときの対処まで、実際の表示画像とコードで解説します。

jQueryのtoggle()とは

toggle()は、選択した要素が表示中なら非表示にし、非表示なら表示するjQueryのメソッドです。英語のtoggleには、2つの状態を交互に切り替えるという意味があります。

クリックで非表示と表示が切り替わる流れ
同じボタンを押すたびに、対象が非表示と表示の2状態を行き来します。

基本形は次のとおりです。#detailsは表示を切り替えたい要素のIDです。

$('#details').toggle();

toggle()を実行するきっかけには、クリックイベントを使うことが多いです。ボタンが押されたときに対象を切り替える基本コードは次のようになります。

$('#toggle-button').on('click', function () {
  $('#details').toggle();
});

toggle()・slideToggle()・fadeToggle()・toggleClass()の違い

jQueryには名前が似た切り替え用メソッドがあります。作りたい動きに合わせて選びましょう。

メソッド切り替わり方向いている用途
toggle()表示・非表示を切り替える。時間を指定すると幅・高さ・透明度などが変化する補足パネル、単純な開閉
slideToggle()高さを動かし、上下へ開閉するFAQ、アコーディオン
fadeToggle()透明度を変えながら表示・非表示を切り替える画像、案内メッセージ
toggleClass()指定したclassを追加・削除する色、アイコン、選択状態などの見た目

単純に瞬時に切り替えるならtoggle()、FAQのように下へ開く動きならslideToggle()が分かりやすいです。見た目だけを変える場合はtoggleClass()を使い、CSSへデザインをまとめます。

補足パネルをtoggle()で開閉する実例

「詳しい内容を見る」ボタンを押すと、コース内容が表示される例を作ります。最初は次のように補足パネルを隠しておきます。

toggleで補足パネルを開く前の画面
初期状態では、青いボタンだけが見え、補足パネルは非表示です。

HTMLでボタンとパネルを関連付ける

HTMLはindex.htmlへ記述します。操作部分にはdivではなく、EnterキーとSpaceキーでも押せるbutton要素を使います。

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>jQuery toggleの基本例</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <main class="demo">
      <p class="demo__label">jQuery .toggle()</p>
      <h1>コースの詳しい内容を切り替えます</h1>
      <p class="lead">
        ボタンを押すたびに、補足パネルの表示と非表示が入れ替わります。
      </p>

      <button
        id="details-button"
        class="toggle-button"
        type="button"
        aria-expanded="false"
        aria-controls="details-panel"
      >
        詳しい内容を見る
        <span aria-hidden="true">+</span>
      </button>

      <section id="details-panel" class="details-panel">
        <p class="details-panel__label">Web制作 入門コース</p>
        <h2>手を動かしながら3つの基本を学びます</h2>
        <ul>
          <li>HTMLでページの構造を作る</li>
          <li>CSSで見た目とレイアウトを整える</li>
          <li>jQueryでクリック操作を追加する</li>
        </ul>
      </section>
    </main>

    <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
    <script src="script.js"></script>
  </body>
</html>

aria-controls="details-panel"は、このボタンがどの要素を操作するかを示します。aria-expanded="false"は、現在閉じている状態を支援技術へ伝えます。

CSSで初期状態を非表示にする

style.cssでは、補足パネルへdisplay: noneを指定します。これが読み込み直後の閉じた状態です。

:root {
  color: #172033;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  background: #edf3fb;
}

* {
  box-sizing: border-box;
}

body {
  min-height: 100vh;
  margin: 0;
  padding: 42px 24px;
  display: grid;
  place-items: center;
}

.demo {
  width: min(940px, 100%);
  min-height: 620px;
  padding: 54px;
  border: 1px solid #dae4f2;
  border-radius: 28px;
  background: #fff;
  box-shadow: 0 26px 72px rgb(42 63 104 / 13%);
}

.demo__label {
  margin: 0 0 12px;
  color: #2966d2;
  font-weight: 800;
  letter-spacing: .08em;
}

h1 {
  margin: 0;
  font-size: clamp(34px, 5vw, 48px);
  line-height: 1.3;
}

.lead {
  margin: 16px 0 28px;
  color: #5a667c;
  font-size: 18px;
  line-height: 1.8;
}

.toggle-button {
  width: 100%;
  min-height: 62px;
  padding: 16px 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border: 0;
  border-radius: 14px;
  color: #fff;
  background: #2966d2;
  font: inherit;
  font-weight: 800;
  cursor: pointer;
}

.toggle-button:focus-visible {
  outline: 4px solid #f5bd42;
  outline-offset: 4px;
}

.toggle-button span {
  font-size: 26px;
  line-height: 1;
}

.details-panel {
  display: none;
  margin-top: 16px;
  padding: 28px;
  border: 1px solid #cbd8ec;
  border-radius: 18px;
  background: #f8faff;
}

.details-panel__label {
  margin: 0;
  color: #2966d2;
  font-weight: 800;
}

.details-panel h2 {
  margin: 8px 0 14px;
  font-size: 25px;
}

.details-panel ul {
  margin: 0;
  padding-left: 1.4em;
  color: #4f5c73;
  line-height: 1.9;
}

@media (max-width: 640px) {
  body {
    padding: 20px;
  }

  .demo {
    min-height: 0;
    padding: 32px 24px;
  }
}

display: noneがないと、最初からパネルが表示され、aria-expanded="false"と実際の状態が食い違います。HTML・CSS・ARIAの初期状態をそろえてください。

クリック時に表示とARIAを切り替える

script.jsでは、現在のaria-expandedを確認して、これから開くかどうかをwillOpenへ保存します。

const $detailsButton = $('#details-button');
const $detailsPanel = $('#details-panel');

$detailsButton.on('click', function () {
  const willOpen = $detailsButton.attr('aria-expanded') === 'false';

  $detailsPanel.toggle();
  $detailsButton.attr('aria-expanded', String(willOpen));
  $detailsButton.find('span').text(willOpen ? '−' : '+');
});

toggle()がパネルを切り替え、attr()が開閉状態を更新します。三項演算子条件 ? 開いた値 : 閉じた値で、アイコンもプラスとマイナスへ切り替えています。

toggleでコース詳細を表示した画面
ボタンを押すと補足パネルが表示され、アイコンとaria-expandedも開いた状態へ変わります。

もう一度押すとパネルは消え、アイコンはプラス、aria-expandedfalseへ戻ります。掲載画像は、このコードをブラウザで実行した結果です。

toggle()の速度を指定する

toggle()へミリ秒を渡すと、指定時間をかけて表示・非表示を切り替えます。1000ミリ秒は1秒です。

$('#toggle-button').on('click', function () {
  $('#details').toggle(300);
});
指定切り替え時間
toggle()アニメーションなし
toggle(300)300ミリ秒
toggle('fast')200ミリ秒
toggle('slow')600ミリ秒

時間が大きいほどゆっくり動きます。1秒以上の長い動きは操作を待たせているように感じやすいため、通常は200〜400ミリ秒程度から調整すると自然です。

連打でアニメーションが続く場合はstop()を使う

アニメーション中に何度も押すと、jQueryの処理待ちに動きがたまり、クリックをやめたあとも開閉が続くことがあります。stop(true, true)で、それまでの待ちを終えてから切り替えます。

$('#toggle-button').on('click', function () {
  $('#details').stop(true, true).toggle(300);
});

最初のtrueは待機中のアニメーションを削除し、2つ目のtrueは現在のアニメーションを終了状態まで進めます。

slideToggle()でFAQアコーディオンを作る

FAQは、回答が上下へ開くslideToggle()と相性のよいUIです。質問を見出しの中のボタンにし、回答のIDとaria-controlsを対応させます。

slideToggleで最初の回答を開いたFAQ
最初の質問だけを開いたFAQです。ボタンの色、記号、aria-expandedが同じ状態を示します。

FAQのHTMLを書く

同じ構造を質問の数だけ繰り返します。IDはページ内で重複しない名前にしてください。

<div class="faq-list">
  <section class="faq-item">
    <h2>
      <button
        class="faq-button"
        type="button"
        aria-expanded="false"
        aria-controls="faq-panel-1"
      >
        初心者でも受講できますか?
        <span aria-hidden="true">+</span>
      </button>
    </h2>
    <div id="faq-panel-1" class="faq-panel">
      <p>
        はい。HTMLを書いたことがない方も、ファイルの作り方から順番に学べます。
      </p>
    </div>
  </section>

  <section class="faq-item">
    <h2>
      <button
        class="faq-button"
        type="button"
        aria-expanded="false"
        aria-controls="faq-panel-2"
      >
        スマートフォンでも学べますか?
        <span aria-hidden="true">+</span>
      </button>
    </h2>
    <div id="faq-panel-2" class="faq-panel">
      <p>
        閲覧はできますが、コードを書く練習にはパソコンの利用がおすすめです。
      </p>
    </div>
  </section>
</div>

ボタンは見出しの唯一の子要素にしています。ネイティブのbuttonを使うため、追加のキーボードイベントを書かなくてもEnterキーとSpaceキーで操作できます。

FAQのCSSを書く

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

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

.faq-item h2 {
  margin: 0;
}

.faq-button {
  width: 100%;
  min-height: 68px;
  padding: 18px 22px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 18px;
  border: 0;
  color: #172033;
  background: #f8faff;
  font: inherit;
  font-size: 19px;
  font-weight: 800;
  text-align: left;
  cursor: pointer;
}

.faq-button[aria-expanded="true"] {
  color: #1f57b8;
  background: #edf4ff;
}

.faq-button:focus-visible {
  outline: 4px solid #f5bd42;
  outline-offset: -4px;
}

.faq-panel {
  display: none;
  padding: 0 22px 22px;
  background: #edf4ff;
}

.faq-panel p {
  margin: 0;
  padding-top: 16px;
  border-top: 1px solid #cbd9ef;
  color: #4f5c73;
  line-height: 1.8;
}

[aria-expanded="true"]は、開いているボタンだけへ適用される属性セレクタです。JavaScriptが更新した状態を利用して色も変えられます。

選んだ質問の次にある回答を開く

クリックされたボタンのaria-controlsから、対応する回答のIDを取得します。これなら質問が複数あっても、選んだ回答だけを開閉できます。

const $faqButtons = $('.faq-button');

$faqButtons.on('click', function () {
  const $button = $(this);
  const willOpen = $button.attr('aria-expanded') === 'false';
  const panelId = $button.attr('aria-controls');
  const $panel = $(`#${panelId}`);

  $panel.stop(true, true).slideToggle(250);
  $button.attr('aria-expanded', String(willOpen));
  $button.find('span').text(willOpen ? '−' : '+');
});

thisは押されたボタンを指します。slideToggle(250)は250ミリ秒で開閉し、stop(true, true)は連打によるアニメーションの滞留を防ぎます。

ブラウザで、クリックだけでなくTabキーで質問へ移動し、EnterキーとSpaceキーでも回答が開閉するか確認してください。ボタンの状態と回答の表示が一致することも重要です。

動きを減らす設定へ対応する

端末で「視差効果を減らす」などを設定している人には、アニメーションを使わず瞬時に切り替えます。JavaScriptからprefers-reduced-motionを確認しましょう。

const reduceMotion = window.matchMedia(
  '(prefers-reduced-motion: reduce)'
).matches;
const duration = reduceMotion ? 0 : 250;

$panel.stop(true, true).slideToggle(duration);

toggleClass()で見た目の状態を切り替える

toggle()は要素そのものの表示・非表示を変えます。色やアイコンなどの見た目を切り替えたい場合は、toggleClass()でclassを追加・削除し、CSSへデザインを記述します。

$('#favorite-button').on('click', function () {
  const $button = $(this);
  const willSelect = $button.attr('aria-pressed') === 'false';

  $button.toggleClass('is-selected', willSelect);
  $button.attr('aria-pressed', String(willSelect));
});

toggleClass('is-selected', true)なら追加、falseなら削除します。トグルボタンのラベルは「お気に入り」のように変えず、押された状態をaria-pressedで伝えます。

あとから追加した要素をtoggleする

Ajaxなどであとから追加されるボタンには、ページ読み込み時の$('.faq-button').on(...)ではイベントが付きません。最初から存在する親要素へイベントを設定します。

$('.faq-list').on('click', '.faq-button', function () {
  const $button = $(this);
  const panelId = $button.attr('aria-controls');

  $(`#${panelId}`).stop(true, true).slideToggle(250);
});

この書き方をイベントの委譲と呼びます。実際のFAQでは、前の例と同じようにaria-expandedとアイコンの更新も追加してください。

toggle()が動かないときの確認項目

jQueryを先に読み込んでいるか

開発者ツールに$ is not definedと表示されたら、jQuery本体が読み込めていません。jQueryのscriptタグを、自分のscript.jsより前へ置きます。

セレクタが対象と一致しているか

$('#details')id="details"を探し、$('.details')class="details"を探します。シャープとピリオド、スペル、大文字・小文字を確認してください。

同じclassの要素がすべて切り替わっていないか

$('.answer').toggle()は、一致する回答をすべて切り替えます。クリックした質問に対応する1つだけを変えるなら、thisnext()、またはaria-controlsから対象を特定しましょう。

CSSの!importantが邪魔をしていないか

display: block !importantなどがあると、jQueryがインラインで指定したdisplay: noneより優先され、隠れない場合があります。開発者ツールのStylesで、取り消し線になっているdisplayを確認してください。

初期表示とaria-expandedが食い違っていないか

回答が見えているのにaria-expanded="false"になっていると、支援技術へ誤った状態を伝えます。閉じて開始するならCSSをdisplay: none、ARIAをfalseにそろえます。

古いtoggleイベントとの違い

古い記事では、複数の関数を渡してクリックごとに交互実行するtoggle(handler, handler)を見かけることがあります。このイベント用シグネチャは非推奨になったあと、jQuery 1.9で削除されました。

// 古い書き方:使用しません
$('#button').toggle(
  function () {
    console.log('1回目');
  },
  function () {
    console.log('2回目');
  }
);

// 現在はclickイベント内で状態を切り替えます
$('#button').on('click', function () {
  $('#details').toggle();
});

表示・非表示を切り替えるtoggle()は別のメソッドとして利用できます。「toggleは削除された」という説明を見たら、イベント用の古い書き方かどうかを確認してください。

よくある質問

最初から表示しておき、最初のクリックで隠せますか?

できます。対象からdisplay: noneを外し、操作ボタンのaria-expandedtrueにします。初期状態をHTML・CSS・ARIAの3か所で一致させてください。

FAQは1つだけ開く形にできますか?

できます。クリックされた回答を開く前に、ほかの回答をslideUp()で閉じ、ほかのボタンのaria-expandedfalseへ戻します。

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

  $('.faq-panel').not($panel).stop(true, true).slideUp(250);
  $('.faq-button').not($button).attr('aria-expanded', 'false');

  $panel.stop(true, true).slideToggle(250);
  $button.attr('aria-expanded', String(willOpen));
});

toggle()だけでハンバーガーメニューを作れますか?

表示の切り替えはできますが、メニューボタンのaria-expanded、メニュー外を押したときやEscキーで閉じる処理、フォーカスの扱いも必要です。単にnav.toggle()だけで完成とは考えないようにしましょう。

まとめ

  • toggle()は表示中の要素を隠し、非表示の要素を表示する
  • FAQの上下開閉にはslideToggle()が使いやすい
  • 色や選択状態はtoggleClass()でCSSのclassを切り替える
  • 操作にはネイティブのbuttonを使い、aria-expandedも更新する
  • アニメーションの連打対策にはstop(true, true)を使う
  • 古いtoggle(handler, handler)は削除済みだが、表示用のtoggle()は利用できる

まずは補足パネルの例をそのまま動かし、マウス、Tabキー、Enterキー、Spaceキーで開閉してみてください。画面の表示、プラス・マイナス、aria-expandedの3つが同じ状態になれば、基本の切り替えは完成です。