sp_menu

【HTML JavaScript】details・summaryタグでアコーディオンメニューを作る方法(開閉アニメーションにも対応)

【HTML JavaScript】details・summaryタグでアコーディオンメニューを作る方法(開閉アニメーションにも対応)

今回はdetails、summaryタグでアコーディオンメニューを作る方法をご紹介します。details、summaryタグの使い方を知りたい方はぜひ参考にしてください。この記事では以下のお悩みを解決します。

  • ・アコーディオンメニューの作り方を知りたい
  • ・コンテンツの開閉をアニメーションにしたい

1. details・summaryタグの基本的な使い方

アコーディオンメニューはクリックすると折りたたまれたコンテンツを開閉できるメニューのことです。LPやコーポレートサイトでは「FAQ(よくある質問)」などでよく使われます。

details、summaryタグを使うとHTMLだけでアコーディオンメニューの基本の形を作ることができます。

details、summaryタグは2025年10月現在、主要ブラウザ(Chrome、Safari、Edge、FireFox)で使うことができます。以下のサイトでブラウザの対応状況が確認できます。

“Details & Summary” | Can I use… Support tables for HTML5, CSS3, etc

1-1. 記述方法

detailsタグはメニュー全体を囲い、summaryタグは見出しを表示します。開閉されるコンテンツ部分はタグをつけずに直接記述できます。必要に応じてdivタグやpタグなどで囲ってもOKです。

summaryタグをクリックするとコンテンツの表示・非表示が切り替わります。

<details>
 <summary>見出し</summary>
 コンテンツ
</details>

実装結果は以下のとおりです。CodePenの「Run Pen」を押して「見出し」をクリックすると「コンテンツ」が開閉します。

See the Pen アコーディオンメニュー1 by CODE SQUARE (@Coffee1610) on CodePen.

1-2. 最初からコンテンツを開いた状態にする

コンテンツを最初から開いた状態にすることもできます。detailsタグにopen属性を追加するとコンテンツが開いた状態を示します。

<!--detailsタグにopen属性を追加する-->
<details open>
 <summary>見出し</summary>
 コンテンツ
</details>

実装結果は以下のとおりです。open属性を追加したアコーディオンメニューは、最初からコンテンツが開いています。

See the Pen アコーディオンメニュー2 by CODE SQUARE (@Coffee1610) on CodePen.

1-3. 矢印を消す方法

summaryタグの横に矢印「▶」「▼」が表示されましたが、これを消したい場合はCSSでsummaryタグ「display: block;」を指定します。

ただし、Safariや古いバージョンのChromeには対応していないのでsummaryタグの疑似要素「::webkit-details-marker」に「display: none;」を指定します。

summary {
 display: block;
}

/* Safariや古いchrome */
summary::-webkit-details-marker {
 display:none;
}

以下の画像はCSSで上記を指定した結果になります。矢印は消えていますね。

矢印を削除後のアコーディオンメニュー

1-4. CSSでスタイルを整える

CSSを使って見た目を整えます。以下はCSSで実装した例になります。

See the Pen アコーディオンメニュー2 by CODE SQUARE (@Coffee1610) on CodePen.

<details class="accordion">
  <summary class="title">見出し1</summary>
  <div class="content">
    <p class="text">コンテンツ1</p>
  </div>
</details>

<details class="accordion">
  <summary class="title">見出し2</summary>
  <div class="content">
    <p class="text">コンテンツ2</p>
  </div>
</details>

<details class="accordion">
  <summary class="title">見出し3</summary>
  <div class="content">
    <p class="text">コンテンツ3</p>
  </div>
</details>
*{
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

body{
  margin: 20px;
}

.accordion {
  margin-bottom: 20px;
}

.title {
  border: 1px solid #333;
  padding: 20px;
  cursor: pointer;
  /*矢印を消す*/
  display: block;
  background-color: #CCC;
  position: relative;
}

.title::-webkit-details-marker {
  /*矢印を消す(Safari,古いChrome)*/
  display: none;
}

.title::before,
.title::after {
  content: "";
  position: absolute;
  background: #333;
  top: 50%;
  translate: 0 -50%;
}

.title::before {
  width: 16px;
  height: 2px;
  right: 20px;
}

.title::after {
  width: 2px;
  height: 16px;
  right: 27px;
  transition: rotate 0.3s;
}

.accordion[open] .title::after {
  rotate: 90deg;
}

.content {
  background-color: #EEE;
  border-left: 1px solid #333;
  border-right: 1px solid #333;
  border-bottom: 1px solid #333;
  max-height: 100px;
}

.text {
  padding: 20px;
  line-height: 2;
}

CSSの解説

18行目:cursor: pointer;

アコーディオンメニューにカーソルをのせると指アイコンに変わり、クリックできることをユーザーに示します。

30-50行目: .title::before, .title::after {~} ~ .title::after{~}

summaryタグの右端にある「+」を擬似要素で作っています。

52-54行目:.accordion[open] .title::after {~}

コンテンツを開くと「+」の縦線が90度回転して「-」になります。

2. コンテンツの開閉をアニメーションにする

HTMLとCSSで実装した場合はコンテンツの開閉は瞬時に切り替わります。ここでは、JavaScriptを使ってアニメーションを追加し、開閉を滑らかにする方法を紹介します。

参考(以下のリンクは動画です。音声に注意してください。)

2-1. 基本パターン

まず、すべてのコンテンツが同時に開くことができる基本パターンの作り方です。JavaScriptでHTMLのデフォルトの開閉動作を無効にして、代わりにWeb Animation APIを使ってアニメーションをつけます。

Web Animation APIはブラウザが提供しているAPIで、JavaScriptだけでCSSのアニメーションを再現できます。以下はWeb Animation APIでアニメーションを実装した結果になります。CodePenで動作を確認してみてください。

See the Pen アコーディオンメニュー3 by CODE SQUARE (@Coffee1610) on CodePen.

.content {
  overflow: hidden;
  background-color: #EEE;
  border-left: 1px solid #333;
  border-right: 1px solid #333;
  border-bottom: 1px solid #333;
}

CSSの解説

57行目:overflow: hidden;

コンテンツの高さが0のときにテキストがはみ出ないようにします。

const accordions = document.querySelectorAll(".accordion");

accordions.forEach((accordion) => {
  const title = accordion.querySelector(".title");
  const content = accordion.querySelector(".content");
  title.addEventListener("click", (e) => {
    e.preventDefault();
    //アニメーション中は操作を無効にする
    if(accordion.dataset.isAnimation === "true") {
      return;
    }
    if(accordion.open) {
      //クリックしたコンテンツを閉じる
      accordion.dataset.isAnimation = "true";
      const closeAnimation = content.animate(
        {
          height: [content.offsetHeight + "px", 0],
          opacity: [1, 0]
        },
        {
          duration: 300,
          easing: "ease"
        },
      );
      closeAnimation.onfinish = () => {
        accordion.removeAttribute("open");
        accordion.dataset.isAnimation = "false";
      }
    }else {
      //クリックしたコンテンツを開く
      accordion.setAttribute("open", "");
      accordion.dataset.isAnimation = "true";
      const openAnimation = content.animate(
        {
          height: [0, content.offsetHeight + "px"],
          opacity: [0, 1]
        },
        {
          duration: 300,
          easing: "ease"
        },
      );
      openAnimation.onfinish = () => {
        accordion.dataset.isAnimation = "false";
      }
    }
  });
});

JavaScriptの解説

1行目:const accordions = document.querySelectorAll(“.accordion”);

すべてのdetailsタグを取得してます。

  • querySelectorAll() :指定されたセレクターに一致する全ての要素を取得し、NodeListで返す。NodeListは配列のようにインデックスを用いて要素にアクセスできる。

3-48行目:accordions.forEach((accordion) => {~});

取得した各detailsタグに対して関数を実行します。

  • NodeList.forEach(callback) :NodeListの各要素に対して関数を実行する。
  • ・callback :NodeList(または配列)の各要素に対して実行する関数

4行目: const title = accordion.querySelector(“.title”);

detailsタグの中のsummaryタグを取得しています。

  • querySelector() :指定されたセレクターに一致する最初の要素を返します。

5行目:const content = accordion.querySelector(“.content”);

detailsタグの中のdivタグを取得しています。

6-47行目:title.addEventListener(“click”, (e) => {~});

summaryタグをクリックすると指定した関数が実行されます。

  • element.addEventListener(type, listener) :windowオブジェクトやHTML要素に特定のイベントが発生したときに実行する関数を登録する。
  • ・type: イベントの種類を指定する。例:click、input、scrollなど
  • ・listener: イベントが発生したときに実行される関数を指定する。

7行目: e.preventDefault();

JavaScriptで開閉を制御するために、summaryタグをクリックしたときのデフォルトの開閉動作を無効にします。

  • event.preventDefault() :要素に設定されているデフォルトの動作を無効にする(例:detailsタグの開閉動作、リンクの遷移、フォーム送信など)
    //アニメーション中は操作を無効にする
    if(accordion.dataset.isAnimation === "true") {
      return;
    }

9-11行目:if(accordion.dataset.isAnimation === “true”) {~};

detailsタグのデータ属性「data-is-animation」がtrueなら処理を終了します。この属性はアニメーションの実行中にtrue、アニメーションが終了するとfalseになります。

アニメーション中にクリックしても、次のアニメーションが実行されないようにするための連打対策です。

※キャメルケースのプロパティ名(isAnimation)はハイフンで区切られて小文字(data-is-animation)に変換されます。

12-47行目:if(accordion.open){~};

コンテンツの開閉のアニメーションを設定しています。detailsタグにopen属性がある場合はコンテンツを閉じる処理、open属性がない場合はコンテンツを開く処理を実行します。

      //クリックしたコンテンツを閉じる
      accordion.dataset.isAnimation = "true";
      const closeAnimation = content.animate(
        {
          height: [content.offsetHeight + "px", 0],
          opacity: [1, 0]
        },
        {
          duration: 300,
          easing: "ease"
        },
      );

14行目: accordion.dataset.isAnimation = “true”;

detailsタグのカスタムデータ属性「data-is-animation」にtrueを設定します。これはアニメーションが実行中であることを示しています。

  • element.dataset.keyname :指定した要素に対してカスタムデータ属性を設定する
  • ・keyname :カスタムデータ属性のプロパティ名(data-◯◯ の「◯◯」にあたる部分)

15-24行目:const closeAnimation = content.animate(~);

Web Animation APIのanimateメソッドを使ってコンテンツを閉じるアニメーションを実行します。

  • element.animate(keyframes, options) :指定した要素に対してアニメーションを実行する
  • ・keyframes :アニメーション開始、中間、終了時点の要素の状態を設定する
  • ・options :アニメーションの時間や速度などを設定する

animateメソッドの引数のkeyframesはアニメーションさせたいプロパティを指定し、値はアニメーションの開始時、終了時の状態は必ず指定し、より細かく設定したい場合は中間の値も追加できます。

//keyframesの設定方法
{
 プロパティ1:[開始時の値, 終了時の値],
 プロパティ2:[開始時の値, 中間の値, 終了時の値],
 .
 .
}

optionsはアニメーションの進行速度や再生時間、繰り返しなどが設定できます。

//optionsの設定方法
{
  オプション1: 値,
  オプション2: 値,
  .
  .
  .
}

17行目:height: [content.offsetHeight + “px”, 0],

18行目:opacity: [1, 0]

keyframesの設定です。コンテンツの高さいっぱいから0に縮めて、透明度を1から0になるアニメーションを実行します。opacityを設定することでふわっとコンテンツが閉じるようになります。

  • element.offsetHeight:指定したコンテンツの要素の高さにpadding、borderを含めた高さを返す
offsetHeightを示したボックスモデル

21行目: duration: 300,

22行目: easing: “ease”

optionsの設定です。durationは再生時間を指定し、この場合0.3秒かけてアニメーションを実行します。easingは進行速度の変化を設定します。”ease”は最初はゆっくり、途中で速く、最後はまたゆっくり進行します。

      closeAnimation.onfinish = () => {
        accordion.removeAttribute("open");
        accordion.dataset.isAnimation = "false";
      }

25-28行目:closeAnimation.onfinish = () => {~};

onfinishはWeb Animation APIのイベントハンドラで、closeAnimationのアニメーションが終了したときに関数を実行します。

26行目: accordion.removeAttribute(“open”);

detailsタグのopen属性を削除します。

  • element.removeAttribute(attrName) :指定した要素の属性を削除する
  • ・attrName :属性名

27行目: accordion.dataset.isAnimation = “false”;

detailsタグのカスタムデータ属性「data-is-animation」をfalseにします。アニメーションが終了した状態を示しています。

      //クリックしたコンテンツを開く
      accordion.setAttribute("open", "");
      accordion.dataset.isAnimation = "true";
      const openAnimation = content.animate(
        {
          height: [0, content.offsetHeight + "px"],
          opacity: [0, 1]
        },
        {
          duration: 300,
          easing: "ease"
        },
      );
      openAnimation.onfinish = () => {
        accordion.dataset.isAnimation = "false";
      }

31行目:accordion.setAttribute(“open”, “”);

detailsタグにopen属性を設定しています。

  • element.setAttribute(name, value) :指定した要素に属性と値を設定する
  • ・name :属性名
  • ・value :値

33-42行目: const openAnimation = content.animate(~);

コンテンツを開くアニメーションを実行しています。

43-45行目: openAnimation.onfinish = () => {~}

openAnimationが終了したら関数を実行します。

2-2. 1つ開くと、他は閉じるパターン

次はコンテンツが開くときに、他の開いているコンテンツがある場合は自動で閉じるパターンを作ります。2-1の基本パターンのJavaScriptのみ変更します。

以下が実装結果です。CodePenの「Run Pen」を押して動きを確認してみてください。

See the Pen アコーディオンメニュー4 by CODE SQUARE (@Coffee1610) on CodePen.

const accordions = document.querySelectorAll(".accordion");

accordions.forEach((accordion) => {
  const title = accordion.querySelector(".title");
  const content = accordion.querySelector(".content");

  //コンテンツを閉じる処理をまとめた関数
  const closeAccordion = (targetAccordion, targetContent) => {
    targetAccordion.dataset.isAnimation = "true";
    const closeAnimation = targetContent.animate(
      {
        height:[targetContent.offsetHeight + "px", 0],
        opacity:[1, 0]
      },
      { 
        duration:300,
        easing:"ease"
      }
    );
      closeAnimation.onfinish = () => {
      targetAccordion.removeAttribute("open");
      targetAccordion.dataset.isAnimation = "false";
    }
 }

  title.addEventListener("click", (e) => {
    e.preventDefault();
    //アニメーション中は操作を無効にする
    if(accordion.dataset.isAnimation === "true") {
      return;
    }
    if(accordion.open) {
      //クリックしたコンテンツを閉じる
      closeAccordion(accordion, content);
    }else {
      const openDetail = document.querySelector(".accordion[open]");
  
      if(openDetail) {
        //すでに開いているコンテンツを閉じる
        const openContent = openDetail.querySelector(".content");
        closeAccordion(openDetail, openContent);
      }
      //クリックしたコンテンツを開く
      accordion.setAttribute("open", "");
      accordion.dataset.isAnimation = "true";

      const openAnimation = content.animate(
        {
          height:[0, content.offsetHeight + "px"], 
          opacity:[0, 1]
        },
        {
          duration:300, 
          easing:"ease"
        }
      );
      openAnimation.onfinish = () => {
        accordion.dataset.isAnimation = "false";
      }
    }
  });
});

JavaScriptの解説

  //コンテンツを閉じる処理をまとめた関数
  const closeAccordion = (targetAccordion, targetContent) => {
    targetAccordion.dataset.isAnimation = "true";
    const closeAnimation = targetContent.animate(
      {
        height:[targetContent.offsetHeight + "px", 0],
        opacity:[1, 0]
      },
      { 
        duration:300,
        easing:"ease"
      }
    );
      closeAnimation.onfinish = () => {
      targetAccordion.removeAttribute("open");
      targetAccordion.dataset.isAnimation = "false";
    }
 }

8-24行目: const closeAccordion = (targetAccordion, targetContent) => {~};

コンテンツを閉じる処理をまとめた関数ですクリック時も他のコンテンツを自動で閉じるときも同じ処理を行うため共通化しています。

指定する引数は以下のとおりです。

targetAccordion: 閉じる対象のdetailsタグを指定

targetContent: 閉じる対象のdivタグを指定

    if(accordion.open) {
      //クリックしたコンテンツを閉じる
      closeAccordion(accordion, content);
    }else {

34行目:closeAccordion(accordion, content);

クリックしたコンテンツを閉じるアニメーションを実行します。

      const openDetail = document.querySelector(".accordion[open]");
  
      if(openDetail) {
        //すでに開いているコンテンツを閉じる
        const openContent = openDetail.querySelector(".content");
        closeAccordion(openDetail, openContent);
      }

36行目:const openDetail = document.querySelector(“.accordion[open]”);

すでに開いているコンテンツのdetailsタグを取得しています。

38-42行目:if(openDetail) {~}

すでに開いているコンテンツがある場合は処理を実行します。

40行目: const openContent = openDetail.querySelector(“.content”);

すでに開いているコンテンツのdivタグ(.content)を取得しています。

41行目:closeAccordion(openDetail, openContent);

すでに開いているコンテンツを自動で閉じるアニメーションを実行します。

3. まとめ

今回は、details・summaryタグを使ってアコーディオンメニューを実装する方法を紹介しました。
HTMLだけでも簡単に作れますが、JavaScriptを組み合わせることで滑らかな開閉アニメーションも実装できます。サイトのFAQやメニューなどにぜひ活用してみてください。

参考になりましたら幸いです。

関連記事