開閉するボックスを作る(HTML/CSS/JS)

前書き

開閉するボックス(アコーディオン)の作り方を掲載しています。

目次

  1. 開閉する
  2. スマホサイズの時だけ開閉する
  3. 隣合わない要素を開閉する
  4. jQueryを使わずに要素を開閉する
  5. detailsタグで開閉する
  6. 参考

開閉する

シンプルに開閉させます。

  • 質問1
    答え1
  • 質問2
    答え2

<ul>
  <li class="list">
    <div class="question" data-question_a>
      <div class="question-text">質問1</div>
    </div>
    <div class="answer" data-answer_a>
      <div class="answer-text">答え1</div>
    </div>
  </li>
  <!-- 2つ目省略 -->
</ul>

.list{
  padding:1em 0; border-bottom: 1px solid #ccc;
  &:nth-of-type(1){
    border-top: 1px solid #ccc;
  }
}
.question{
  position: relative;cursor: pointer;
  &::before{
    position: absolute; top: 1em; right: 1em; content: "+";
  }
  &.active{
    &::before{
      content: "-";
    }
  }
}
.answer{
  display: none;
}
.question-text,
.answer-text{
  position: relative; padding: 1em 3em;
  &::before{
    position: absolute; top: 0.875em; left: 0; width: 2em; height: 2em; line-height: 2;
    font-weight: bold; text-align: center; border-radius: 50%;
  }
}
.question-text{
  &::before{
    content:"Q"; color: #fff; background: #845080;
  }
}
.answer-text{
  &::before{
    content:"A"; background: #e7d4e3;
  }
}

$("[data-question_a]").on("click",function(){
  const question = $(this);
  const answer = $(this).next();

  question.toggleClass("active");
  answer.slideToggle(200);
});

スマホサイズの時だけ開閉する

スマホサイズの時だけ開閉させます。PCサイズの時は開けたままにします。

  • 質問1
    答え1
  • 質問2
    答え2

<ul>
  <li class="list">
    <div class="question question-b" data-question_b>
      <div class="question-text">質問1</div>
    </div>
    <div class="answer-b" data-answer_b>
      <div class="answer-text">答え1</div>
    </div>
  </li>
  <!-- 2つ目省略 -->
</ul>

//前略 追加分のみ
.question-b{
  @media screen and (min-width: 768px)
    cursor: default;
    &::before{
      display: none;
    }
  }
}
.answer-b{
  display: none;
  @media screen and (min-width: 768px)
    display: block;
  }
}

$("[data-question_b]").on("click",function(){
  const ww = window.innerWidth;
  const question = $(this);
  const answer = $(this).next();

  if(ww < 768){
    if( question.hasClass("active")){
      question.removeClass("active");
      answer.slideUp(200,function(){
        //スライドアップしたら、style属性のdisplay:noneを消す
        answer.css({'display':''});
      });
    }else{
      question.addClass("active");
      answer.slideDown(200);
    }
  }
});

隣合わない要素を開閉する

data属性の値が一致する要素を開閉します。

  • 質問1
    答え1
  • 質問2
    答え2

<ul>
  <li class="list">
    <div class="question" data-question_c="1">
      <div class="question-text">質問1</div>
    </div>
    <div class="answer" data-answer_c="1">
      <div class="answer-text">答え1</div>
    </div>
  </li>
  <li class="list">
    <div class="question" data-question_c="2">
      <div class="question-text">質問2</div>
    </div>
    <div class="answer" data-answer_c="2">
      <div class="answer-text">答え2</div>
    </div>
  </li>
</ul>

//前述のscssと同じ

$("[data-question_c]").on("click",function(){
  const question = $(this);
  const num = $(this).data("question_c");
  const answer = $('[data-answer_c="' + num + '"]');

  question.toggleClass("active");
  answer.slideToggle(200);
});

jQueryを使わずに要素を開閉する

jQueryを使わない場合は、開いた時の高さをどう取得するか問題になります。
こちらの例では、高さを取得する為だけに、div要素を一つ追加しています。

  • 質問1
    答え1
  • 質問2
    答え2

<ul>
  <li class="list">
    <div class="question" data-question_d>
      <div class="question-text">質問1</div>
    </div>
    <div class="answer-d" data-answer_d>
      <div data-size_d>
        <div class="answer-text">答え1</div>
      </div>
    </div>
  </li>
  <li class="list">
    <div class="question" data-question_d>
      <div class="question-text">質問2</div>
    </div>
    <div class="answer-d" data-answer_d>
      <div data-size_d>
        <div class="answer-text">答え2</div>
      </div>
    </div>
  </li>
</ul>

//前略 追加分のみ
.answer-d{
  overflow: hidden; height: 0;
}

document.querySelectorAll('[data-question_d]').forEach((question,i) => {
  question.addEventListener('click',function(){
    const answer = document.querySelectorAll('[data-answer_d]')[i];
    const size = document.querySelectorAll('[data-size_d]')[i];
    let active = false;

    if(!active){
      const timeStart = Date.now();
      const timeTotal = 200;
      let timePassed = 0;
      const heightBefore = answer.offsetHeight;
      const heightMax = size.offsetHeight;

      const toggle = (timePassed) => {
        if( heightBefore == 0 ){
          question.classList.add("active");
          if(timePassed >= timeTotal){
            answer.style.height = "auto";
          }else{
            answer.style.height = heightMax * timePassed / timeTotal + 'px';
          }
        }else{
          question.classList.remove("active");
          if(timePassed >= timeTotal){
            answer.style.height = "";
          }else{
            answer.style.height = heightBefore * ( 1 - timePassed / timeTotal ) + 'px';
          }
        }
      }

      const tick = () => {
        timePassed = Date.now() - timeStart;
        toggle(timePassed);
        if(timePassed < timeTotal){
          active = true;
          requestAnimationFrame( tick ); //繰り返し
        }else{
          cancelAnimationFrame( tick );
          timePassed = 0;
          active = false;
        }
      }
      requestAnimationFrame( tick );
    }
  });
});

detailsタグで開閉する

htmlのdetailsタグを使っています。二つ目のタグにはopen属性を記載しています。

質問1
答え1
質問2
答え2

<details>
  <summary>質問1</summary>
  <div class="details-text">答え1</div>
</details>
<details open>
  <summary>質問2</summary>
  <div class="details-text">答え2</div>
</details>

details{
  padding:1em 0; border-bottom: 1px solid #ccc;
  &:nth-of-type(1){
    border-top: 1px solid #ccc;
  }
}
summary{
  outline: none; cursor: pointer;
}
.details-text{
  padding: 0 0 0 1em; margin: 1em 0 0;
}

参考

レスポンシブデザインでスマホだけアコーディオン

CSSすら不要!detailsとsummaryタグで作る簡単アコーディオン

関連記事

画面全体に表示するメニューを作る(HTML/CSS/JS)

CSSを使って自動で番号を振る

WEBページの部品作り

先頭に戻る
ページの先頭に戻る