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

前書き

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

目次

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

開閉する

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

  • 質問1

    答え1

  • 質問2

    答え2


<ul>
  <li class="list">
    <div class="question question-a" data-question_a>
      <p class="question-text">質問1</p>
    </div>
    <div class="answer answer-a" data-answer_a>
      <p class="answer-text">答え1</p>
    </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: "-";
    }
  }
}
.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;
  }
}
//個別設定
.answer-a{
  display: none;
}

$("[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>
      <p class="question-text">質問1</p>
    </div>
    <div class="answer answer-b" data-answer_b>
      <p class="answer-text">答え1</p>
    </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 question-c" data-question_c="1">
      <p class="question-text">質問1</p>
    </div>
    <div class="answer answer-c" data-answer_c="1">
      <p class="answer-text">答え1</p>
    </div>
  </li>
  <li class="list">
    <div class="question question-c" data-question_c="2">
      <p class="question-text">質問2</p>
    </div>
    <div class="answer answer-c" data-answer_c="2">
      <p class="answer-text">答え2</p>
    </div>
  </li>
</ul>

//個別設定
.answer-c{
  display: none;
}

$("[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 question-d" data-question_d>
      <p class="question-text">質問1</p>
    </div>
    <div class="answer answer-d" data-answer_d>
      <div data-inner_d>
        <p class="answer-text">答え1</p>
      </div>
    </div>
  </li>
  <!-- 2つ目省略 -->
</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 inner = document.querySelectorAll('[data-inner_d]')[i];
    let active = false;

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

      if( heightBefore == 0 ){
        question.classList.add("active");
      }else{
        question.classList.remove("active");
      }

      const answerToggle = ( progress ) => {
        if( heightBefore == 0 ){
          if( progress > 1 ){
            heightAfter = "auto";
          }else{
            heightAfter = heightMax * progress + 'px';
          }
        }else{
          if( progress > 1 ){
            heightAfter = "";
          }else{
            heightAfter = heightBefore * ( 1 - progress ) + 'px';
          }
        }
        answer.style.height = heightAfter;
      }

      const tick = () => {
        const progress =  ( Date.now() - timeStart ) / timeTotal; //経過率
        answerToggle( progress );
        if( progress < 1 ){
          active = true;
          requestAnimationFrame( tick ); //繰り返し
        }else{
          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)

フォトギャラリーを作る(HTML/CSS/JS)

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

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

WEBページの部品作り

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