GSAPタイムラインで作る、ボタンバウンスアニメーション

ホバー時にボタンがスライムのように弾むアニメーションの実装方法について解説しています。複数ステップにわたる動きを細かく制御するため、GSAPのタイムラインを使用しています。

実装例

早速ですが下記が実装例です。ボタンにホバーするとアニメーションが再生されます。

ボタンバウンスアニメーション

scaleXscaleYを組み合わせて横に潰れ・縦に伸びる動きを表現し、最後にelasticのeaseで弾むような余韻をつけています。CTAボタンなどに使うと、控えめながら印象に残るインタラクションになります。

解説

HTML、CSS、JavaScriptの処理を順を追って解説していきます。

HTMLとCSS

HTMLは次のとおりです。

HTML
<a href="#" class="c-button js-button">すべて見る</a>

アニメーションの対象となるボタンにjs-buttonクラスを付与しています。CSSは見た目のスタイリングのみで、アニメーション自体はJavaScript側で制御しています。

CSS
.c-button {
--color-button-bg: #2f7fb2;
--color-button-text: #fff;
display: inline-block;
font-weight: 700;
font-size: 20px;
color: var(--color-button-text);
background-color: var(--color-button-bg);
border-radius: 99rem;
padding: 0.75em 2.5em;
}

JavaScript

JavaScriptのアニメーションに関するコードは以下のとおりです。

JavaScript
// ボタンのバウンスアニメーション本体
const playBounceAnimation = (target, onCompleteCallback) => {
const tl = gsap.timeline({
defaults: { transformOrigin: "center bottom" },
onComplete: onCompleteCallback,
});
tl.to(target, {
scaleX: 1.05,
scaleY: 0.95,
yPercent: 0,
duration: 0.12,
ease: "power2.in",
});
tl.to(target, {
scaleX: 0.95,
scaleY: 1.05,
yPercent: -25,
duration: 0.2,
ease: "power2.inOut",
});
tl.to(target, {
scaleX: 1,
scaleY: 1,
yPercent: 0,
duration: 0.2,
ease: "power2.inOut",
});
tl.to(
target,
{ scaleX: 1.15, scaleY: 0.85, duration: 0.05, ease: "power2.out" },
"-=0.05",
);
tl.to(target, {
scaleX: 1,
scaleY: 1,
duration: 0.4,
ease: "elastic.out(1.2, 0.4)",
});
};
// ボタンのアニメーションを初期化
const initButtonAnimations = () => {
const buttons = document.querySelectorAll(".js-button");
buttons.forEach((button) => {
if (!button) return;
let isAnimating = false;
button.addEventListener("mouseenter", () => {
if (isAnimating) return;
isAnimating = true;
playBounceAnimation(button, () => {
isAnimating = false;
});
});
});
};
initButtonAnimations();

一言でまとめると、ホバー時にボタンを横に潰して跳ね上がらせ、着地の瞬間にもう一度潰してからelasticで元の形に戻しています。transformOrigin: "center bottom"により、底辺を軸にした弾む動きになっています。

JavaScriptのポイント

タイムラインの組み立て方と、isAnimatingフラグの役割を解説します。

タイムラインアニメーションで動きを表現

このアニメーションは5つのステップで構成されています。各ステップでdurationeaseにそれぞれ異なる値を設定することで、単調にならない有機的な動きを実現しています。

ステップ動きdurationease
1横に広がり縦に潰れる(踏み込み)0.12spower2.in
2縦に伸びて上に跳ねる0.2spower2.inOut
3元のサイズに戻り着地0.2spower2.inOut
4着地の瞬間に再び潰れる0.05spower2.out
5elasticで元の形に復帰0.4selastic.out(1.2, 0.4)

4つ目のステップは"-=0.05"で3つ目と0.05秒重ねて開始しています。着地と同時に潰れる動きを作ることで、スライムのような粘り気のある印象になります。タイムラインのposition parameterを使うと、このような重なりを数値で直感的に指定できます。

フラグの意味

initButtonAnimations関数内のisAnimatingフラグには、次の2つの役割があります。

  • 他のボタンに影響を与えない:forEachのコールバック内でlet isAnimating = falseを宣言しているため、ボタンAとボタンBが同時にホバーされても、互いのフラグに影響しません。
  • アニメーション実行中の多重発火をブロック:アニメーション中の再ホバーでplayBounceAnimationが重複実行されるのを防ぎます。onCompleteコールバックでフラグをfalseに戻すことで、次のホバーに備えています。

CSS実装との比較

似たような見た目のアニメーションはCSSのみでも実装可能です。ただし、以下の点でGSAPの方が有利です。

  • アニメーション中の再発火制御:CSSでは再生中に再度ホバーした場合の挙動を細かく制御できません。
  • ステップごとのease設定:各キーフレームに異なるeasingを割り当てるにはanimation-timing-functionをキーフレームごとに指定する必要があり、調整に手間がかかります。
  • キーフレームの重なり:"-=0.05"のようなステップ間の重なりは、CSSではanimation-delayの負の値で再現できますが、全体のタイムラインを俯瞰しながら細かな調整をする必要があります。

GSAPとCSSのみを比較すると次の表のとおりです。

項目GSAPCSSのみ
見た目の再現○(elasticは近似)
調整のしやすさ△(%とeaseを手計算)
JavaScript依存ありなし
タイムラインの重なり簡単可能だが面倒
再生制御柔軟限定的

JavaScriptを追加できるプロジェクトであれば、GSAPのタイムラインを使った方が意図した動きに素早くたどり着けると思います。一方、JavaScriptを使わずに済ませたい場合はCSSでの実装も選択肢になります。

この記事を書いた人

写真:ブール 代表 西川雅人

Masato Nishikawa

ウェブ制作会社・デザイナー様向けに、コーディングやWordPress実装を承っているフリーランスです。繁忙期のサポートや、継続的な外注先をお探しでしたら、お気軽にお声がけください。

コーディングや
WordPress実装の
ご相談を承っています。

新規制作から運用フェーズの修正まで柔軟に対応します。
お見積りや実装可否の確認など、お気軽にご連絡ください。