jQueryを使わず、cssアニメーションで高さ可変のアコーディオンを実装した際の、試行錯誤の記録です。
jQueryの場合
jQueryなら、アコーディオンメニューも簡単に実装できます。
<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>
$('.js-button').on('click', function() {
$(this).next('.js-accordion').slideToggle();
});
cssアニメーション
jQueryなしで実装する場合は、cssアニメーションを使います。
パフォーマンス的にも、jQueryのアニメーションではなくcssのアニメーションを使用した方が良いです。
(なので、jQueryは開閉のトリガーだけに使用し、アニメーションはcssで行う方法もあります。)
ただ、アコーディオンするコンテンツの高さが可変の場合、ちょっと面倒です。。
まずは、開閉のトリガーのみを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;
だと、アニメーションしてくれない。
.accordion {
height: 0;
transition: height 0.3s linear;
}
.accordion.is-expand {
height: auto;
}
max-heightをアニメーションする
max-height: auto;
もアニメーションしてくれないので、十分に大きな数値を設定してあげて対応すれば動く。
.accordion {
max-height: 0;
transition: max-height 0.3s linear;
}
.accordion.is-expand {
max-height: 9999px;
}
JavaScriptでmax-heightを取得
ただその9999px
というマジックナンバーには明確な意味がないので、JavaScriptで中身の高さを取得して設定するようにしてみました。
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;
});
});
.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
.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)を使う
変形してしまうのが許容できるなら。。
.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...
.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
のみで高さが変更可能)の場合は良さそう。
.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
<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>
/* 外側の枠の高さを設定 */
.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で複雑な処理を書くくらいなら、いろいろ試してみると良いと思いました。