🐕

jQueryのslideToggleは偉大だ、という話

2023/03/06に公開

jQueryのslideToggleを使えばすぐできる、アニメーションでスライドするアコーディオンの実装。
それを、素のJavaScriptを使って書き換えてみたらなかなか大変だった、という話です。
やりたかったのは、以下。

HTMLの構造

HTML
<div>
  <article class="js_accordion">
    <p>にゃーん</p>
  </article>
  <article class="js_accordion">
    <p>わんわん</p>
  </article>

  <div class="accordion_btn_wrap">
    <button class="js_accordion_btn"></button>
  </div>
</div>

jQueryで書くと数行

jQuery
$(function(){
  $('.js_accordion_btn').on('click', function(){
    let $accordions = $(this).parents('.accordion_btn_wrap').siblings('.js_accordion');
    $accordions.slideToggle();
  });
});

素のJavaScriptで書き直した

JavaScript
/* 素のJavaScriptでの、アニメーションアコーディオン実装 */
// DOMの構築後に実行
document.addEventListener('DOMContentLoaded', function(){
  // 開閉ボタン
  const slideToggleBtns = document.querySelectorAll(".js_accordion_btn");
  slideToggleBtns.forEach(function(slideToggleBtn) {
    // 各ボタンにイベントを付与
    slideToggleBtn.addEventListener("click", (event) => {
      // ボタンから辿って、スライドさせたい要素を取得
      let slides = event.currentTarget.parentNode.parentNode.getElementsByClassName('js_accordion');
      // 配列に変換
      let slides_array = Array.from(slides);
      // 配列の要素を一つずつslideToggle関数で開閉
      slides_array.forEach(function(el) {
        slideToggle(el, 300);
      });
    });
  });
});

// slideUp
const slideUp = (el, duration = 300) => {
  el.style.height = el.offsetHeight + "px";
  el.offsetHeight;
  el.style.transitionProperty = "height, margin, padding";
  el.style.transitionDuration = duration + "ms";
  el.style.transitionTimingFunction = "ease";
  el.style.overflow = "hidden";
  el.style.height = 0;
  el.style.paddingTop = 0;
  el.style.paddingBottom = 0;
  el.style.marginTop = 0;
  el.style.marginBottom = 0;
  setTimeout(() => {
    el.style.display = "none";
    el.style.removeProperty("height");
    el.style.removeProperty("padding-top");
    el.style.removeProperty("padding-bottom");
    el.style.removeProperty("margin-top");
    el.style.removeProperty("margin-bottom");
    el.style.removeProperty("overflow");
    el.style.removeProperty("transition-duration");
    el.style.removeProperty("transition-property");
    el.style.removeProperty("transition-timing-function");
  }, duration);
};

// slideDown
const slideDown = (el, duration = 300) => {
  el.style.removeProperty("display");
  let display = window.getComputedStyle(el).display;
  if (display === "none") {
    display = "block";
  }
  el.style.display = display;
  let height = el.offsetHeight;
  el.style.overflow = "hidden";
  el.style.height = 0;
  el.style.paddingTop = 0;
  el.style.paddingBottom = 0;
  el.style.marginTop = 0;
  el.style.marginBottom = 0;
  el.offsetHeight;
  el.style.transitionProperty = "height, margin, padding";
  el.style.transitionDuration = duration + "ms";
  el.style.transitionTimingFunction = "ease";
  el.style.height = height + "px";
  el.style.removeProperty("padding-top");
  el.style.removeProperty("padding-bottom");
  el.style.removeProperty("margin-top");
  el.style.removeProperty("margin-bottom");
  setTimeout(() => {
    el.style.removeProperty("height");
    el.style.removeProperty("overflow");
    el.style.removeProperty("transition-duration");
    el.style.removeProperty("transition-property");
    el.style.removeProperty("transition-timing-function");
  }, duration);
};

// slideToggle
const slideToggle = (el, duration = 300) => {
  if (window.getComputedStyle(el).display === "none") {
    return slideDown(el, duration);
  } else {
    return slideUp(el, duration);
  }
};

おわかりいただけただろうか

jQueryのslideToggle、すごくない?

自分が知る限りだと、jQueryのslideToggleを簡単に代替する手法は見当たりませんでした。特に、スライド内容の高さが決まっていない場合ですね。
cssのtransitionでアニメーションさせるにも、max-heightを決め打ちする必要があるので、高さがマチマチの場合は使えません。

まあ昨今は、DOM操作をjsでやること自体がレトロなのかもしれませんが、やる必要があるときにはjQuery御大は今でもたいへん便利だなと思いました。

個人的希望としては、ゆくゆくは<details>要素にcssでアニメーションを指定できるようになってほしいです。

ではまた。

参考文献

なお、slideToggle関数自体は、こちらの記事を参考にいたしました。
大変ありがたかったです m(_ _)m
https://web-dev.tech/front-end/javascript/slide-methods-on-vanilla-javascript/

追記

bootstrapの機能で、簡単にできることが判明😆
https://getbootstrap.jp/docs/5.0/components/collapse/

Discussion