スクロールで物語る(Scroll Storytelling)

このページでは、GSAPのScrollTriggerで実装するscrub・pin・横スクロール・パララックスなど、スクロールに連動した高度なアニメーションパターンをデモとコードで確認できます。

このページはGSAP(ScrollTrigger)専用です。scrub・pin・ピン留めコンテンツ切り替え・横スクロール変換などの高度なスクロール演出は、GSAPのScrollTriggerでしか実現が難しい実装です。 通常のスクロール連動アニメーション(フェードイン・スライドイン)は スクロールで見せる(Scroll Reveal) も参照してください。

↓ 下にスクロールして6つのデモを体験してください。各セクションのコードは下部で確認できます。

プレビュー

読み込み中...

各デモのコード解説

各デモをクリックして実装コードを確認してください。すべてのデモは useGSAP({ scope: containerRef }) でスコープを設定し、アンマウント時に自動クリーンアップされます。

01 進捗バー(scrub)

スクロール量に連動して左から右へ伸びるプログレスバー。scrub: true でスクロール位置に即時追従します。

tsx
gsap.to('.progress-bar', {
  scaleX: 1,
  ease: 'none',
  scrollTrigger: {
    trigger: containerRef.current, // スクロール対象の要素
    start: 'top top',              // 要素の上端がビューポート上端に来たとき開始
    end: 'bottom bottom',          // 要素の下端がビューポート下端に来たとき終了
    scrub: true,                   // スクロール量に連動(trueで即時追従)
  },
});

// HTML: origin-left で左から伸びるようにする
// <div class="progress-bar h-1 bg-pink-400 origin-left" style={{ transform: 'scaleX(0)' }} />

02 テキスト演出(scrub)

複数行のテキストが1行ずつスクロールに連動してフェードイン。gsap.utils.toArray で一括設定できます。

tsx
// gsap.utils.toArray で複数要素を取得してまとめて設定
gsap.utils.toArray<HTMLElement>('.text-line').forEach((line) => {
  gsap.from(line, {
    opacity: 0,
    y: 40,
    scrollTrigger: {
      trigger: line,
      start: 'top 80%', // 要素の上端がビューポートの80%位置に来たとき
      end: 'top 50%',   // 要素の上端がビューポートの50%位置に来たとき
      scrub: 1,         // 数値指定でなめらかな追従(秒単位の遅延)
    },
  });
});

// HTML: 初期状態を opacity: 0 にしておくとフラッシュを防げる
// <p class="text-line" style={{ opacity: 0 }}>テキスト</p>

03 画像ズーム(scrub)

スクロールに連動して画像が scale: 1 → 1.3 にゆっくりズーム。overflow: hidden で拡大をクリップします。

tsx
gsap.to('.zoom-inner', {
  scale: 1.3,
  ease: 'none',
  scrollTrigger: {
    trigger: '.zoom-section',
    start: 'top bottom',  // セクションがビューポート下端に入ったとき開始
    end: 'bottom top',    // セクションがビューポート上端を超えたとき終了
    scrub: true,
  },
});

// HTML: overflow: hidden で拡大した部分をクリップ
// <section class="zoom-section overflow-hidden">
//   <div class="zoom-inner min-h-screen" style={{ background: 'linear-gradient(...)' }} />
// </section>

04 パララックス

背景が前景より遅い速度で動くパララックス効果。yPercent で要素を縦方向に移動させます。

tsx
gsap.to('.parallax-bg', {
  yPercent: -30, // 自身の高さの30%分だけ上に移動(ページより遅い速度)
  ease: 'none',
  scrollTrigger: {
    trigger: '.parallax-section',
    start: 'top bottom',
    end: 'bottom top',
    scrub: true,
  },
});

// HTML: 背景を150%の高さにして余白を持たせる
// <section class="parallax-section overflow-hidden relative">
//   <div
//     class="parallax-bg absolute inset-x-0"
//     style={{ height: '150%', top: '-25%', background: 'linear-gradient(...)' }}
//   />
//   <div class="relative z-10">前景コンテンツ</div>
// </section>

05 ピン留めコンテンツ切り替え(pin)

セクションをピン留めした状態で、スクロールに連動して中のスライドが順番に切り替わります。

tsx
const slides = gsap.utils.toArray<HTMLElement>('.pin-slide');

const tl = gsap.timeline({
  scrollTrigger: {
    trigger: '.pin-section',
    pin: true,                            // セクションをピン留め(固定)
    start: 'top top',
    end: `+=${slides.length * 400}`,    // スライド数 × 400px のスクロール距離
    scrub: 1,
  },
});

// スライドを順番にフェード切り替え
slides.forEach((slide, i) => {
  if (i > 0) {
    tl.from(slide, { autoAlpha: 0, y: 30, duration: 1 });
    tl.to(slides[i - 1], { autoAlpha: 0, y: -30, duration: 1 }, '<');
  }
});

// autoAlpha は opacity と visibility をまとめて制御するGSAPの特殊プロパティ

06 横スクロール演出(pin)

縦スクロールを横移動に変換。セクションをピン留めしてカードを横に流します。

tsx
const cards = gsap.utils.toArray<HTMLElement>('.h-card');
const cardWidth = cards[0].offsetWidth; // 各カードの幅を取得

gsap.to('.h-track', {
  x: -(cards.length - 1) * cardWidth, // カード(n-1)枚分だけ左に移動
  ease: 'none',
  scrollTrigger: {
    trigger: '.horizontal-section',
    pin: true,                                          // セクションをピン留め
    scrub: 1,
    end: () => `+=${(cards.length - 1) * cardWidth}`, // スクロール距離を動的計算
  },
});

// HTML: overflow: hidden でスクロールバーを隠す
// <section class="horizontal-section overflow-hidden">
//   <div class="h-track flex" style={{ width: `${cards.length * 100}%` }}>
//     {cards.map(card => (
//       <div class="h-card flex-shrink-0" style={{ width: `${100 / cards.length}%` }}>
//         {card.label}
//       </div>
//     ))}
//   </div>
// </section>

GSAP ScrollTrigger のポイント

  • gsap.registerPlugin(ScrollTrigger, useGSAP) はコンポーネント外(モジュールレベル)で一度だけ呼ぶ
  • scrub: true はスクロールに即時追従。scrub: 1 は1秒の遅延でなめらかに追従
  • pin: true でセクションをピン留め。スクロールしても要素が固定され、end に到達すると解除
  • start / end"top top" のように「要素の位置 ビューポートの位置」で指定。"+= 400" はピクセル数での相対指定
  • useGSAP({ scope: containerRef } ) でクリーンアップを自動化。アンマウント時にScrollTriggerも破棄される
  • autoAlpha は GSAP 独自のプロパティで opacityvisibility を連動させる
💡 上記のデモは、React / Next.jsです。Next.jsの基本セットアップは 公式ドキュメントを参照してください。