GSAPでシンプルにアコーディオン実装のメモ
はじめに
WebサイトのUIでよく見かけるアコーディオンを、アニメーションライブラリのGSAPで実装するメモです。GSAP(GreenSock Animation Platform)は、JavaScriptでリッチなアニメーションを簡単・高性能に実装できるライブラリです。
※本メモではアコーディオンの動作に必要なコードのみ記載していますので、全てのコードはcodepenからご確認ください。
DEMO
See the Pen accordion by KAZUKI YOKOUCHI (@tone-design) on CodePen.
HTML
<div class="accordion js-accordion" data-accordion-state="close">
<div class="accordion__header js-accordion-opener">
<p>アコーディオンを開く</p>
</div>
<div class="accordion__content js-accordion-content">
<p>アコーディオンの内容がはいります。アコーディオンの内容がはいります。アコーディオンの内容がはいります。アコーディオンの内容がはいります。</p>
</div>
</div>
アコーディオン全体のクラス名を
、クリックで開閉する部分を js-accordion
、実際に開閉されるコンテンツを js-accordion-opener
としています。js-accordion-content
※CSS用のクラスと区別するため、JavaScriptで操作する要素は接頭辞を js-
としています。
data-accordion-state
はアコーディオンの開閉状態を管理するためのカスタムデータ属性です。
CSS の属性セレクタを用いて、開閉時のCSSによるスタイル分けのため利用します。
今回はプラス/マイナスのアイコンを切り替えをCSSで設定しています。
閉じているときは close
、開いているときは open
とし、JavaScriptで切り替えます。
初期値の状態は閉じているのでclose
です。
CSS
.accordion__content {
display: none;
}
CSS は .accordion__content
に初期値として display: none;
を設定し、アコーディオンの開閉コンテンツを非表示にするのみです。
[data-accordion-state='close'] .accordion__header:after {
/*閉じている時(プラスアイコン) */
content: '\2b';
}
[data-accordion-state='open'] .accordion__header:after {
/*開いている時(マイナスアイコン) */
content: '\f068';
}
// アコーディオンの要素を全て取得
const accordions = document.querySelectorAll('.js-accordion');
// アニメーション中かどうかを管理するフラグ
// weakmapを使うことで、DOM要素に直接プロパティを追加せずに状態を管理できる
const animatingFlag = new WeakMap();
accordions.forEach((accordion) => {
// 各アコーディオン内の開閉要素とコンテンツを取得
const opener = accordion.querySelector('.js-accordion-opener');
const content = accordion.querySelector('.js-accordion-content');
// 初期化 / 全てのアコーディオンは「アニメーションしていない」状態からスタート
animatingFlag.set(accordion, false);
opener.addEventListener('click', () => {
// すでにアニメーション中なら処理をしない
if (animatingFlag.get(accordion)) return;
// アニメーション中フラグをセット
animatingFlag.set(accordion, true);
// 進行中のGSAPアニメーションを強制終了して競合を防ぐ
gsap.killTweensOf(content);
// 開閉状態を取得(true:open, false:close)
const isOpen = accordion.getAttribute('data-accordion-state') === 'open';
if (!isOpen) {
// ▼ 開く処理(closeの状態) ▼
// openへ状態をセット
accordion.setAttribute('data-accordion-state', 'open');
// 要素を表示して高さ0にセット
gsap.set(content, {
display: 'block',
height: 0,
});
// scrollHeightまでアニメーションで展開
gsap.to(content, {
height: content.scrollHeight,
duration: 0.3,
onComplete: () => {
// 開いた後は高さをautoに戻してレスポンシブに対応
accordion.setAttribute('data-accordion-state', 'open');
gsap.set(content, {
height: 'auto',
});
// アニメーション中フラグを解除
animatingFlag.set(accordion, false);
},
});
} else {
// ▼ 閉じる処理(openの状態) ▼
// closeへ状態をセット
accordion.setAttribute('data-accordion-state', 'close');
// 高さ0にして、アニメーション後に非表示にする
gsap.to(content, {
height: 0,
duration: 0.3,
onComplete: () => {
gsap.set(content, { display: 'none' });
// アニメーション中フラグを解除
animatingFlag.set(accordion, false);
},
});
}
});
});
開閉アニメーション中かどうかの管理
const animatingFlag = new WeakMap();
アコーディオンの開閉アニメーション中の開閉連打による意図しない動作を防ぐため、今回はJavaScriptの組み込みオブジェクトのWeakMap
による「開閉アニメーション中かどうか」の状態を管理・チェックします。
accordions.forEach((accordion) => {
// 各アコーディオン内の開閉要素とコンテンツを取得
const opener = accordion.querySelector('.js-accordion-opener');
const content = accordion.querySelector('.js-accordion-content');
// 初期化 / 全てのアコーディオンは「アニメーションしていない」状態からスタート
animatingFlag.set(accordion, false);
初期化として、全てのアコーディオン要素に「アニメーションしていない状態」としてfalse
をセットします。
opener.addEventListener('click', () => {
// すでにアニメーション中なら処理をしない
if (animatingFlag.get(accordion)) return;
// アニメーション中フラグをセット
animatingFlag.set(accordion, true);
各アコーディオン要素の開閉ボタンをクリックしたとき、animatingFlag
がtrue
であれば「すでにアニメーション中」と判断して処理を中断し、連打による意図しない動作を防ぎます。false
の場合はanimatingFlag
にtrue
をセットし、「アニメーション中の状態」としています。
gsap.set(content, {
display: 'block',
height: 0,
});
// scrollHeightまでアニメーションで展開
gsap.to(content, {
height: content.scrollHeight,
duration: 0.3,
onComplete: () => {
// 開いた後は高さをautoに戻してレスポンシブに対応
accordion.setAttribute('data-accordion-state', 'open');
gsap.set(content, {
height: 'auto',
});
// アニメーション中フラグを解除
animatingFlag.set(accordion, false);
},
});
開く処理
content.scrollHeight
は、要素の「中身の高さ(スクロール可能な実際の高さ)」を取得するプロパティです。
CSSで height: auto;
を指定したときと同じような「コンテンツの実寸の高さ」を数値(px)で返します。
アコーディオンの開閉アニメーションでは、CSSで .accordion__content
を display: none;
にして非表示にしているので、開くときにはまず gsap.set()
を使って要素を display: block
に変更し、さらに height: 0px
を指定して、高さが0の状態からアニメーションを開始します。
その後、gsap.to()
を使って content.scrollHeight
の値(要素の中身の実際の高さ)までアニメーションさせることで、アコーディオンをスムーズに開きます。
※アニメーションが完了したら、animatingFlag
を false
にセットし「アニメーションしていない状態」とします。
閉じる処理
gsap.to(content, {
height: 0,
duration: 0.3,
onComplete: () => {
gsap.set(content, { display: 'none' });
// アニメーション中フラグを解除
animatingFlag.set(accordion, false);
},
});
閉じるときは、gsap.to()
で height: 0
を指定し、アコーディオンのコンテンツを縮める(0px)にするアニメーションを実行します。これで開閉の一連の動作が完了です。
※アニメーションが完了したら、animatingFlag
を false
にセットし「アニメーションしていない状態」とします。
おわりに
アコーディオン単体で GSAP を導入することは少ないかと思いますが、サイト全体で GSAP を利用している場合にはシンプルに実装でき、便利です。