LoginSignup
38
30

More than 3 years have passed since last update.

CSSアニメーションで高さ可変のアコーディオン

Last updated at Posted at 2019-09-25

jQueryを使わず、cssアニメーションで高さ可変のアコーディオンを実装した際の、試行錯誤の記録です。

jQueryの場合

jQueryなら、アコーディオンメニューも簡単に実装できます。

accordion.html
<ul>
  <li>
    <button class="js-button">ボタン</button>
    <div class="accordion js-accordion">
      <p>アコーディオンするコンテンツ</p>
    </div>
  </li>
  <li>
    <button class="js-button">ボタン2</button>
    <div class="accordion js-accordion">
      <p>アコーディオンするコンテンツ2アコーディオンするコンテンツ2</p>
    </div>
  </li>
</ul>
jquery-accordion.js
$('.js-button').on('click', function() {
  $(this).next('.js-accordion').slideToggle();
});

cssアニメーション

jQueryなしで実装する場合は、cssアニメーションを使います。
パフォーマンス的にも、jQueryのアニメーションではなくcssのアニメーションを使用した方が良いです。
(なので、jQueryは開閉のトリガーだけに使用し、アニメーションはcssで行う方法もあります。)

ただ、アコーディオンするコンテンツの高さが可変の場合、ちょっと面倒です。。

まずは、開閉のトリガーのみをjsで設定します。

trigger.js
const button = document.querySelectorAll('.js-button');
const openClass = 'is-expand';
Array.prototype.forEach.call(button, (el) => {
  const contents = el.nextElementSibling;
  el.addEventListener('click', (e) => {
    e.preventDefault();
    el.classList.toggle(openClass);
    contents.classList.toggle(openClass);
  });
});

heightをアニメーションする

height: 0;からheight: auto;だと、アニメーションしてくれない。

height-auto.css
.accordion {
  height: 0;
  transition: height 0.3s linear;
}
.accordion.is-expand {
  height: auto;
}

max-heightをアニメーションする

max-height: auto;もアニメーションしてくれないので、十分に大きな数値を設定してあげて対応すれば動く。

max-height-auto.css
.accordion {
  max-height: 0;
  transition: max-height 0.3s linear;
}
.accordion.is-expand {
  max-height: 9999px;
}

JavaScriptでmax-heightを取得

ただその9999pxというマジックナンバーには明確な意味がないので、JavaScriptで中身の高さを取得して設定するようにしてみました。

set-max-height.js
const button = document.querySelectorAll('.js-button');
const openClass = 'is-expand';
Array.prototype.forEach.call(button, (el) => {
  el.addEventListener('click', (e) => {
    e.preventDefault();
    const contents = el.nextElementSibling;
    const outerHeight = contents.clientHeight;
    // 子要素の高さを取得
    const innerHeight = contents.children[0].clientHeight;

    el.classList.toggle(openClass);
    contents.classList.toggle(openClass);

    // 子要素の高さを親要素のmax-heightに設定
    contents.style.maxHeight = outerHeight === 0 ? `${innerHeight}px` : 0;
  });
});
set-max-height.css
.accordion {
  /* 子要素の高さは維持しながら、.accordionを見えなくする */
  max-height: 0;
  min-height: 0;
  overflow-y: hidden;
  transition: all .5s ease-in-out;
}
.accordion.is-expand {
  transition: all .2s ease-in-out;
}

【ポイント】
子要素.accordion > pの高さは維持しながら、.accordionを非表示にすることで、max-heightに指定するコンテンツの高さを取得することができます。

しかし、(スマホのデバイスを回転させるなど)画面がリサイズされるとアコーディオンパネルの高さが変わり、コンテンツが見切れてしまう場合が。。
resize時にmax-heightを設定し直すように修正すれば良いが、どんどん複雑になっていく・・・。

max-heightを使わずにアニメーション

max-heightを使わずに、それっぽく見せるには・・・?。
先人も、試行錯誤している模様・・・参考にさせていただきました。
https://stackoverflow.com/questions/3508605/how-can-i-transition-height-0-to-height-auto-using-css

margin-top: -100%;(transform: translateY(-100%);)を使う

中身が上から下りてくる感じになる(スライダー的な動き)
参考:
https://codepen.io/diegopardo/full/pvkjx

margin.css
.accordion {
  margin-top: -100%;
  /* transform: translateY(-100%); でも同じ */
  transition: all .2s ease-out;
}
.accordion > p {
  opacity: 0;
}
.accordion.is-expand {
  margin-top: 0;
 /* transform: translateY(0); */
}
.accordion.is-expand > p {
  opacity: 1;
}

transform: scaleY(0)を使う

変形してしまうのが許容できるなら。。

transform.css
.accordion {
  transform: scaleY(0);
  transition: all .2s ease-out;
}
.accordion.is-expand {
  transform: scaleY(1);
}

height以外の要素でそれっぽく見せる

height:auto;でなぜかアニメーションしているものがある!?
調べてみると、transition-property: all;height以外の要素がアニメーションすることで、それっぽく見えているようでした。
padding, margin, line-height etc...

not-height.css
.accordion {
  height: 0;
  margin-top: 0;
  overflow: hidden;
  opacity: 0;
  padding: 0;
  transition: all .2s ease-out;
}
.accordion.is-expand {
  height: auto;
  margin-top: 16px;
  opacity: 1;
  padding: 16px 5px 0 20px;
  transition: all .2s ease-out;
}

子要素を、高さ以外でアニメーションさせる

子要素が単純なもの(line-heightのみで高さが変更可能)の場合は良さそう。

child.css
.accordion > p {
  line-height: 0;
  opacity: 0;
  padding: 0 10px;
  transition: all .2s ease-out;
}
.accordion.is-expand > p {
  line-height: 1.25;
  opacity: 1;
  padding: 16px 10px;
}

サンプル:
https://codepen.io/kzmhrd/pen/zYObKmr

子要素の位置をずらして、それっぽく見せる

参考:
https://codepen.io/LasyAsCat/pen/KdxLRj

translateY.html
<ul>
  <li>
    <button class="js-button">ボタン</button>
    <div class="accordion js-accordion">
      <div class="accordion__contents">
        <div class="accordion__inner">アコーディオンするコンテンツ</div>
      </div>
    </div>
  </li>
  <li>
    <button class="js-button">ボタン2</button>
    <div class="accordion js-accordion">
      <div class="accordion__contents">
        <div class="accordion__inner">アコーディオンするコンテンツ2アコーディオンするコンテンツ2</div>
      </div>
    </div>
  </li>
</ul>
translateY.css
/* 外側の枠の高さを設定 */
.accordion {
  max-height: 0;
  overflow: hidden;
  transition: max-height .3s linear;
}
.accordion.is-expand {
  max-height: 100%;
  transition: max-height .4s linear;
}

/* コンテンツを上下に動かしておいて、見えない状態にしておく */
/* コンテンツを上に */
.accordion__contents {
  overflow: hidden;
  transform: translateY(-100%);
  transition: transform .3s linear;
}
/* コンテンツを下に */
.accordion__inner {
  overflow: hidden;
  transform: translateY(100%);
  transition: transform .3s linear;
}

/* コンテンツの位置を0にして表示させる */
.accordion.is-expand .accordion__contents {
  transform: translateY(0);
  transition: transform .2s linear;
}
.accordion.is-expand .accordion__inner {
  transform: translateY(0);
  transition: transform .2s linear;
}

サンプル:
https://codepen.io/kzmhrd/pen/pozYNvo

.accordionの高さが固定されていればいい感じですが、高さを変化させるために、結局、max-heightとの合わせ技になってしまったため、ちょっと動きが微妙。。
今回は、アコーディオン内の要素が複雑だったため、これを採用しました。

まとめ

cssアニメーションには正解がないことが分かりました。。
アイデア一つでいろいろできるので、jsで複雑な処理を書くくらいなら、いろいろ試してみると良いと思いました。

38
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
30