【脱jQuery】Vanilla JSとanime.jsでjQueryのslideUp/slideDownを再現する
最近はウェブサイトの制作でjQueryを使わないようにしています。
jQuery無しで実装しようとしたときに意外と困るのがアニメーションでして、大抵のアニメーションはスタイルシートでも再現可能なんですが、slideUpとslideDownの動きがなかなか再現できなくて、時間がかかりました。
今回はVanilla JSとanime.jsライブラリでslideUp、slideDownを再現する方法を備忘録として記載します。
ソースコード
See the Pen Slide toggle with vanilla js and anime.js (without jQuery) by JeyJapan (@st-lite-jn) on CodePen.
実装方法の解説
HTMLは親要素のul要素内のli要素の子要素に、サブメニューとしてさらにul要素が存在し、親要素のli要素に開閉ボタンを設置しています。開閉ボタンには「btn」というclassを設定します。
スタイルシートでサブメニューを表示しないようにしていますが、「display: none;」「 visibility: hidden;」で二重で非表示化しています。
JavaScriptでは「button」classと、定義したサブメニューの開閉ボタンをdocument.querySelectorAllメソッドで取得します。document.querySelectorAllメソッドで取得できるのはNodeListなのでforEachメソッドでElementオブジェクトを取得し、addEventListenerで開閉処理の関数を発火させます。なおIE11ではforEachメソッドでNodeListからElementオブジェクトを取得することはできません。
開閉ボタンをDocument.querySelectorメソッドやDocument.getElementByIdメソッドを使って取得すると、Elementオブジェクトが取得できるので、forEachメソッドは不要ですが、それだと1ページ内に複数のボタンが存在していると対応ができません。
const $buttons = document.querySelectorAll(".button");
$buttons.forEach($button => {
$button.addEventListener("click", slideToggle);
});
開閉処理の関数では、まずクリックした要素を取得し、そのクリックした要素の隣の要素、つまりサブメニューを取得します。
const $subMenu = e.currentTarget.nextElementSibling;
次にif文でクリックした要素に「is-active」クラスが無いか判定して処理を分岐しています。
if(!e.currentTarget.classList.contains("is-active"))
ボタンに「is-active」クラスがない場合、つまりサブメニューが表示していない場合、
最初にクリックボタンに「is-active」クラスを追加します。
次にサブメニューをdisplay:blockでブロック要素にした上で高さを取得します。
このときスタイルシートでvisibility:hiddenと指定しているので、高さは取得できるけどブラウザからは見えないままです。
そのあとサブメニューの高さ(height)を0にした上で、visibility:visibleで非表示化を解除します。
e.currentTarget.classList.add("is-active");
$subMenu.style.display = "block";
const subMenuHeight = $subMenu.clientHeight;
$subMenu.style.height = 0;
$subMenu.style.visibility = "visible";
高さが動的に変化する処理はanime.jsで行っています。
「keyframes」でアニメーションの開始時と終了時の状態を指定しています。開始時は高さが0の状態で、終了時は先程取得したサブメニューの高さになっている状態を指定しています。
durationでアニメーションをスピードです。500だと0.5秒間でアニメーションを実行します。
easingはアニメーションの変化の仕方を指定します。種類に応じでアニメーションの滑らかさや序盤・終盤でのスピードなどが変わります。easingの種類についてはこちらのサイトが参考になります。
anime({
targets: $subMenu,
keyframes:[
{height: 0},
{height: `${subMenuHeight}px`}
],
duration: 500,
easing: 'easeInSine',
});
クリックした要素に「is-active」classがある場合、つまりサブメニューが開いている状態の場合は、逆のことを指定することで、slideUpを再現しています。
e.currentTarget.classList.remove("is-active");
const subMenuHeight = $subMenu.clientHeight;
anime({
targets: $subMenu,
keyframes:[
{height: `${subMenuHeight}px`},
{height: 0}
],
duration: 500,
easing: 'easeInSine',
complete: () => {
$subMenu.style.cssText = null;
}
});