un tronc
ホームページ制作

GSAPのScrollTriggerで、スクロールに合わせてキャラクターを動かす手順

こんにちは!アントロンのNaokoです。

ページをスクロールすると、キャラクターが一緒に動くサイトを見たことはありませんか?

このサイトに付ける予定のチャットボットでは、呼び出しボタンに妖精のキャラクターを使うことにしています。その妖精を、スクロールに合わせて飛ばす試作をしました。使ったのは、GSAP(ジーサップ)というJavaScriptのライブラリと、そのプラグインのScrollTriggerです。

コードは、AIのClaude Code に「こう動かしたい」と頼んで書いてもらいました。私はブラウザで動きを見て、直してほしい所を伝える役です。10月1日の夜、約2時間で、練習の丸から妖精の完成まで進みました(Claude Code の使い方はこちらの記事にまとめています)。

この記事では、最初の練習から妖精が羽ばたくまでの手順と、途中でつまずいたところをまとめます。本番のサイトにこの動きを入れるかは、まだ決めていません。

このサイトのトップページのスクリーンショットに妖精を置いた試作

GSAPとScrollTriggerでできること

GSAPは、Webページの要素を動かすためのJavaScriptのライブラリです。ScrollTriggerはGSAPのプラグイン(機能を足すための追加の部品)の1つで、動きをスクロールの位置と結びつけられます。

今回は、HTML・CSS・JavaScriptだけのページに、次の2つのファイルを jsDelivr(ライブラリを配信しているCDN)から読み込みました。バージョンは3.13.0です。

  • gsap.min.js(GSAP本体)
  • ScrollTrigger.min.js(ScrollTrigger)

指定できる項目の説明は、公式のドキュメント(ScrollTrigger)にあります。

手順1:まず丸を1つ動かす

いきなりキャラクターを動かす前に、オレンジの丸で練習しました。500pxスクロールする間に、丸が左へ200px動き、1.5倍に大きくなる動きです。

gsap.registerPlugin(ScrollTrigger);
gsap.set(".character", { xPercent: -50, yPercent: -50 }); // 画面の中央に置く

gsap.to(".character", {
  x: -200,      // 左へ200px
  scale: 1.5,   // 1.5倍
  ease: "none", // スクロール量に比例させる
  scrollTrigger: {
    trigger: ".scroll-area",
    start: "top top", // ページの一番上から
    end: "+=500",     // 500pxスクロールしたら終わり
    scrub: true,      // スクロールに連動。戻すと逆に動く
    markers: true,    // 開始と終了の位置を画面に出す
  },
});

ポイントは scrub: true です。これを付けると、スクロールを戻したときに動きも逆向きに戻ります。

markers: true にすると、画面の右に start・end(動かす要素の側の位置)と scroller-start・scroller-end(画面の側の位置)の線が出ます。思った位置で動き始めないときは、まずこれを付けて確かめると原因を探しやすくなります。

スクロール前と500pxスクロールしたあとの丸。右上に開始と終了の線が出ている

中央寄せは、CSSの transform: translate(-50%, -50%) ではなく、GSAPの xPercent と yPercent で指定しました。GSAPも要素の transform を書き換えるので、動かす指定と中央寄せをGSAP側にそろえています。

手順2:妖精を浮かせて、上から下へ動かす

練習のあと、Canvaの画像生成で作った妖精の絵(背景が透明なPNG)に置き換えました。動きは2つです。

  • ふわふわ浮く:スクロールと関係なく、上下12pxの動きをずっと繰り返す
  • ページの進み具合に合わせて、画面の上から下へ移動する
// ふわふわ浮く(yoyo で行って戻る、repeat: -1 でずっと繰り返す)
gsap.to(bob, { y: -12, duration: 1.4, ease: "sine.inOut", yoyo: true, repeat: -1 });

// ページの一番上から一番下までで、画面の上から下へ
gsap.fromTo(guide,
  { y: () => innerHeight * 0.06 },
  {
    y: () => innerHeight - guide.offsetHeight - 24,
    ease: "none",
    scrollTrigger: { trigger: ".page", start: "top top", end: "bottom bottom", scrub: 0.6, invalidateOnRefresh: true },
  }
);

scrub に数字(ここでは0.6)を入れると、その秒数をかけてスクロールの位置に追いつくので、動きがなめらかになります。invalidateOnRefresh: true は、画面の大きさが変わったときに位置を計算し直すための指定です。

手順3:羽を分けて、羽ばたかせる

静止画1枚のままだと、羽だけを動かせません。そこで、Claude Code に頼んで、1枚の絵を「体」「左の羽」「右の羽」の3枚の画像に切り分けてもらいました。3枚とも同じ大きさ(360×365px)で、同じ位置に重ねると元の絵に戻ります。

妖精の絵を左の羽・右の羽・体の3枚に分け、重ねて元の絵に戻したところ

羽は、肩の付け根を軸にして横幅を縮めると、羽ばたいて見えます。軸はCSSの transform-origin で、左の羽は47% 45%、右の羽は53% 45%にしました。

const flap = gsap.timeline({ repeat: -1, yoyo: true });
flap.to(".wing", { scaleX: 0.6, duration: 0.14, ease: "sine.inOut" });
flap.timeScale(0.3); // 止まっているときはゆっくり
羽の横幅が1のときと0.6のときの妖精

最初は止まっているときも速く羽ばたいていましたが、試作を見て「止まっているときはもっとゆっくりがいい」と頼んで変えてもらいました。いまは止まっているときは0.3倍、スクロール中は速さに合わせて最大2.5倍です。

手順4:進む向きを見せる

スクロールの向きと速さは、ScrollTriggerの onUpdate で受け取れます。self.direction が向き(下へ進むと1、上へ戻ると-1)、self.getVelocity() が速さです。これを使って、次の3つを足しました。

  • 速さに合わせて前後に傾ける(下へ進むと前のめり、上へ戻るとのけぞる。最大35度)
  • 向きが変わった瞬間に、進む方向へ14px跳ねる
  • キラキラ光る粒を、進む向きと反対へ流す(8px進むごとに1粒)
下へ進むときは粒が上へ、上へ戻るときは粒が下へ流れる

つまずいたところ:速くスクロールすると絵がにじむ

最初の試作では、上下の向きが変わったときに、妖精を左右に反転させていました。ところが、速くスクロールすると絵がにじみ、とくに向きを切り替えた直後はにじんだままになりました。

反転は、横幅を1から-1へ0.3秒かけて変える動きにしていました。途中で横幅が0に近い状態を通るので、そのときの粗い絵が引き伸ばされて残っていた、というのが Claude Code の見立てです。私が確かめたのは、直したあとの画面でにじみが出なくなったことです。向きを切り替えた直後のにじみは、反転を一瞬で切り替える形に変えたら出なくなりました。

羽ばたきも横幅を縮める動きなので、次の2つを入れています。

  • 羽の横幅は0.6より細くしない
  • 羽の画像に、CSSの will-change: transform を付ける

なお、左右の反転はそのあとやめました。正面を向いた絵なので、反転しても、どちらを向いているのか分かりづらかったためです。代わりに、手順4のキラキラ光る粒で向きを見せています。

妖精を押すとチャット画面が開く

最後に、妖精を押すと少し跳ねてからチャット画面が開き、妖精は隠れるようにしました。チャット画面を閉じると、妖精が戻ってきます。チャット画面の見た目は、チャットボットの試作で決めたものと同じです。いまは見た目だけで、送信しても返事は来ません。画面の中の会話は、見た目を確かめるために先に入れておいた見本です。

妖精を押して開いたチャット画面。中の会話は見た目を確かめるための見本

あわせて、パソコンやスマホで「視差効果を減らす」などの、動きを減らす設定をしている人には、浮く・羽ばたく・傾く・跳ねる動きと、キラキラ光る粒を止めるようにしています。JavaScriptの matchMedia で、prefers-reduced-motion の設定を確かめています。

まずは丸1つから試してみてください

スクロールに合わせた動きは、いきなりキャラクターで作るより、丸1つで start・end・scrub の動きを確かめてからのほうが、うまく動かないときに原因を探しやすくなります。手順1のコードを、スクロールできる長さ(例:高さ300vh)の箱と、画面に固定した丸を1つ置いたHTMLに入れて、x や end の数字を変えたときに動きがどう変わるかを見てみてください。

サイトにチャットボットを付けたい方は、AIチャットボット導入サービスのページもご覧ください。

最後まで読んでくださりありがとうございました!

コメント