スクロール連動(Scroll Animation)

スクロールに連動して要素をアニメーションさせる実装をFramer MotionとIntersection Observer APIで比較

🔗 このページはFramer Motion / Intersection Observer APIを使ったReact実装です。バニラJSでの実装はスクロールアニメーションページを参照してください。
Framer Motion

読み込み中...

tsx
'use client';
import { useRef } from 'react';
import { motion } from 'framer-motion';

export default function ScrollDemo() {
  const containerRef = useRef<HTMLDivElement>(null);

  return (
    <div ref={containerRef} style={{ height: '400px', overflowY: 'auto' }}>
      {/* フェードイン */}
      <motion.div
        initial={{ opacity: 0 }}
        whileInView={{ opacity: 1 }}
        transition={{ duration: 0.6, ease: 'easeOut' }}
        viewport={{ once: true, amount: 0.3, root: containerRef }}
      >
        フェードイン
      </motion.div>

      {/* スライドイン(下から) */}
      <motion.div
        initial={{ opacity: 0, y: 40 }}
        whileInView={{ opacity: 1, y: 0 }}
        transition={{ duration: 0.6, ease: 'easeOut' }}
        viewport={{ once: true, amount: 0.3, root: containerRef }}
      >
        スライドイン
      </motion.div>

      {/* スケールイン */}
      <motion.div
        initial={{ opacity: 0, scale: 0.8 }}
        whileInView={{ opacity: 1, scale: 1 }}
        transition={{ duration: 0.6, ease: 'easeOut' }}
        viewport={{ once: true, amount: 0.3, root: containerRef }}
      >
        スケールイン
      </motion.div>

      {/* スタガー(複数要素を順番に) */}
      {[0, 1, 2].map((i) => (
        <motion.div
          key={i}
          initial={{ opacity: 0, y: 20 }}
          whileInView={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.5, delay: i * 0.1 }}
          viewport={{ once: true, root: containerRef }}
        >
          スタガー {i + 1}
        </motion.div>
      ))}
    </div>
  );
}

jQueryで書くとこうなる

jQueryではスクロールイベントを監視して要素の位置を毎回計算する方法が一般的でしたが、パフォーマンスの問題がありました。Reactでは Framer Motion の whileInView やブラウザネイティブの IntersectionObserver を使うことで、より効率的にスクロール連動アニメーションを実装できます。
js
// scroll イベントで要素の位置を判定してアニメーション
$(window).on('scroll', function () {
  $('.animate-on-scroll').each(function () {
    const elementTop = $(this).offset().top;
    const viewportBottom = $(window).scrollTop() + $(window).height();
    if (elementTop < viewportBottom - 50) {
      $(this).addClass('is-visible');
    }
  });
});

// waypoints プラグインを使った方法
$('.animate-on-scroll').waypoint(function () {
  $(this.element).addClass('is-visible');
}, { offset: '80%' });

// スクロール量に連動して透明度を変える
$(window).on('scroll', function () {
  const scrollTop = $(window).scrollTop();
  const opacity = Math.min(scrollTop / 300, 1);
  $('#hero').css('opacity', 1 - opacity);
});
💡 上記のデモは、React / Next.jsです。Next.jsの基本セットアップは 公式ドキュメントを参照してください。

実装方法の比較

項目Framer MotionIntersection Observer
記述量whileInViewで1要素に数行useRef + useEffectのセットアップが必要
スクロール量連動useScroll + useTransformで簡単に実現自前でscrollイベント + 計算が必要
onceオプションviewport={{ once: true }}で一度だけ実行disconnect()で手動解除が必要
パフォーマンス内部でIntersectionObserverを使用ネイティブAPIで軽量
設定コスト高:framer-motionのインストールが必要低:追加ライブラリ不要

whileInView のポイント

  • viewport={{ once: true }} で一度表示されたら再度アニメーションしない
  • viewport={{ amount: 0.3 }} で要素の30%が見えたときにトリガー
  • • スクロール可能なコンテナ内で使う場合は viewport={{ root: containerRef }} を指定する
  • useScroll + useTransform でスクロール量に応じた連続的な値変化が可能
  • • jQueryの $(window).on('scroll') と違いメインスレッドをブロックしない

🤖 AIプロンプトテンプレート

Next.js + Framer Motion でスクロール連動アニメーションを実装してください。
- 要素がビューポートに入ったときにフェードイン+スライドアップ
- whileInView と viewport={{ once: true }} を使用
- 複数の要素が順番に現れるスタガー効果も加える
- TypeScript / Tailwind CSS v4 を使用

⚠️ このプロンプトはあくまでたたき台です。AIの回答はモデルやバージョン、会話の文脈によって毎回異なります。意図通りに動かない場合は、条件を追記・修正してお使いください。