【GSAP】play()とreverse()で超簡単!timelineを使ったハンバーガーメニュー開閉アニメーション
Webサイトのスマートフォン表示や、モダンなデザインのPCサイトでよく見かける「全画面のハンバーガーメニュー」。 メニューが開く際に「背景が広がってから、ナビゲーションの文字が順番にフワッと現れる」といったリッチな演出を入れたい場面がありますよね。
CSSだけでも実装は可能ですが、アニメーションのタイミング(遅延)を計算するのは非常に面倒です。 そこで大活躍するのが、GSAPの timeline 機能です!
timeline を使えば、複数のアニメーションを連結させてひとつの「タイムライン」として管理できます。さらに play()(再生)と reverse()(逆再生)のメソッドを使うことで、開く時も閉じる時も非常にシンプルに制御することが可能です。 今回は、メニュー開閉アニメーションの基本をデモと共にご紹介します!
デモ動作
See the Pen GSAP timelineで作るメニュー開閉 by mikanbako (@mikanbako) on CodePen.
demo: GSAP timelineで作るメニュー開閉
実装コードの紹介
【HTML】
<header class="l-header">
<div class="l-header__inner">
<h1 class="l-header__logo">LOGO</h1>
<!-- ハンバーガーボタン -->
<button class="c-hamburger js-hamburger" aria-label="メニューを開閉する">
<span></span>
<span></span>
<span></span>
</button>
</div>
</header>
<!-- 全画面メニュー -->
<nav class="p-fullscreen-menu js-menu">
<ul class="p-fullscreen-menu__list">
<!-- アニメーションで順に表示させるためのクラス(js-menu-link)を付与 -->
<li class="p-fullscreen-menu__item js-menu-link"><a href="#">Home</a></li>
<li class="p-fullscreen-menu__item js-menu-link"><a href="#">About</a></li>
<li class="p-fullscreen-menu__item js-menu-link"><a href="#">Services</a></li>
<li class="p-fullscreen-menu__item js-menu-link"><a href="#">Contact</a></li>
</ul>
</nav>
<main class="l-main">
<p>メインコンテンツがここに入ります。</p>
<p>右上のボタンをクリックしてね!</p>
</main>
【CSS】
/* ヘッダー周り */
.l-header {
position: fixed;
top: 0;
left: 0;
width: 100%;
padding: 20px;
z-index: 100; /* メニューより手前にボタンを配置するため一番高くする */
}
.l-header__inner {
display: flex;
justify-content: space-between;
align-items: center;
}
.l-header__logo {
font-size: 24px;
font-weight: bold;
}
/* ハンバーガーボタンのスタイル(今回はシンプルなCSSアニメーションで×印に) */
.c-hamburger {
position: relative;
width: 40px;
height: 40px;
cursor: pointer;
z-index: 100;
}
.c-hamburger span {
position: absolute;
left: 5px;
width: 30px;
height: 2px;
background-color: #1bb4d4;
transition: transform 0.3s ease-out, opacity 0.3s ease-out;
}
.c-hamburger span:nth-child(1) { top: 10px; }
.c-hamburger span:nth-child(2) { top: 19px; }
.c-hamburger span:nth-child(3) { top: 28px; }
/* activeクラス付与時(バツ印になる) */
.c-hamburger.is-active span:nth-child(1) {
transform: translateY(9px) rotate(45deg);
}
.c-hamburger.is-active span:nth-child(2) {
opacity: 0;
}
.c-hamburger.is-active span:nth-child(3) {
transform: translateY(-9px) rotate(-45deg);
}
/* 全画面メニュー(GSAPで表示させるため初期状態は非表示) */
.p-fullscreen-menu {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100svh;
background-color: rgba(255, 255, 255, 0.95);
display: flex;
justify-content: center;
align-items: center;
z-index: 50;
visibility: hidden; /* 最初は隠しておく */
opacity: 0;
}
.p-fullscreen-menu__list {
display: flex;
flex-direction: column;
gap: 24px;
text-align: center;
}
.p-fullscreen-menu__item a {
color: #1bb4d4;
font-size: 24px;
font-weight: bold;
}
.l-main {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 16px;
height: 100svh;
font-size: 18px;
}
【JavaScript】
// 要素の取得
const hamburger = document.querySelector('.js-hamburger');
const menu = document.querySelector('.js-menu');
const menuLinks = document.querySelectorAll('.js-menu-link');
// 1. タイムラインの初期化(最初は停止状態にしておく)
const tl = gsap.timeline({ paused: true });
// 2. タイムラインにアニメーションを登録(順番に実行される)
tl.to(menu, {
duration: 0.4,
autoAlpha: 1, // opacityとvisibilityを同時にコントロールする便利なプロパティ
ease: "power2.out"
})
// メニュー背景が出た後、リンクを順番に(stagger)下からフワッと出す
.from(menuLinks, {
duration: 0.5,
y: 30, // 30px下から
opacity: 0,
stagger: 0.1, // 0.1秒ずらしながら順番に表示
ease: "back.out(1.5)" // 少し跳ねるようなイージング
}, "-=0.2"); // 直前のアニメーションの終了0.2秒前に食い込んで開始させる
// 3. クリックイベントで再生・逆再生をコントロール
hamburger.addEventListener('click', () => {
// ボタンのクラスを切り替えてバツ印にする
hamburger.classList.toggle('is-active');
// クラスが付与されていれば再生、そうでなければ逆再生
if (hamburger.classList.contains('is-active')) {
tl.play(); // 【処理①】アニメーションを正方向に再生
} else {
tl.reverse(); // 【処理②】アニメーションを逆方向に巻き戻す
}
});
コードの解説・こだわったポイント
gsap.timeline({ paused: true }) でアニメーションを待機させる
// 1. タイムラインの初期化(最初は停止状態にしておく)
const tl = gsap.timeline({ paused: true });
今回のように「ボタンがクリックされてから」動かしたい場合は、タイムライン作成時に paused: true を指定します。これにより、ページ読み込み時に勝手にアニメーションが始まってしまうのを防ぐことができます。
.to() と .from() を繋げて連続処理を作る
// 2. タイムラインにアニメーションを登録(順番に実行される)
tl.to(menu, {
...
})
.from(menuLinks, {
...
}, "-=0.2"); // 直前のアニメーションの終了0.2秒前に食い込んで開始させる
タイムラインインスタンス(tl)に対して、メソッドをチェーンさせることで、順番に実行されるアニメーションを定義できます。 また、末尾に "-=0.2" のようなパラメータを渡すことで、「前の処理が終わるのを完全に待たず、少し食い気味に次の処理をスタートさせる」といった、より自然で滑らかな連携が可能です。
便利なプロパティ stagger
.from(menuLinks, {
duration: 0.5,
y: 30,
opacity: 0,
stagger: 0.1, // 0.1秒ずらしながら順番に表示
ease: "back.out(1.5)"
}, "-=0.2");
GSAPの強力な機能のひとつが staggerです。 複数の要素(今回はメニューの各リンク js-menu-link)に対して stagger: 0.1 と指定するだけで、0.1秒ずつタイミングをずらしながら順番にアニメーションを実行してくれます。CSSの nth-child などで一つ一つ delay を計算する手間が完全に省けます!
play() と reverse() で状態を管理
// クラスが付与されていれば再生、そうでなければ逆再生
if (hamburger.classList.contains('is-active')) {
tl.play(); // 【処理①】アニメーションを正方向に再生
} else {
tl.reverse(); // 【処理②】アニメーションを逆方向に巻き戻す
}
メニューの「開く」と「閉じる」という2つの状態を、tl.play() と tl.reverse() のたった2行でコントロールしています。 GSAPが「現在どのアニメーションまで進行しているか」を記憶してくれているため、例えば「メニューが開いている途中で閉じるボタンを連打された」場合でも、アニメーションが破綻することなくスムーズに巻き戻ってくれます。
補足:公式ドキュメント
より詳しい仕様や最新の対応状況については、以下の公式ドキュメントをご参照ください。
まとめ
今回は、GSAPの timeline を使った全画面メニューの開閉アニメーションをご紹介しました。
「背景を表示させる」→「文字を順番に出す」という一連の処理が、非常にシンプルなJavaScriptコードで実装できるのがGSAPの魅力です。 stagger による順次表示はメニュー以外にも、カード一覧の表示やページ読み込み時のローディングアニメーションなど、あらゆる場面で活用できます。
これからもGSAPの機能を使いこなし、ユーザーの目を惹きつつ、コードの保守性も高いモダンな実装を心掛けていきたいと思います!