T-CREATOR

Motion(旧 Framer Motion)スクロール進行度マッピング早見表:offset・range・transform の定番式

Motion(旧 Framer Motion)スクロール進行度マッピング早見表:offset・range・transform の定番式

Web アプリケーションやランディングページを制作する際、スクロールに連動したアニメーションは訪問者の目を引き、情報の流れをスムーズに伝える強力な手段となります。Motion(旧 Framer Motion)は React コンポーネントに豊かなアニメーションを追加できるライブラリとして広く使われていますが、スクロール進行度をアニメーションにマッピングする設定は、offset・range・transform の 3 つのパラメータが絡み合うため、初めて触れる方には少し複雑に感じられるかもしれません。

この記事では、Motion のスクロールアニメーションで頻出する offset・range・transform の定番パターンを早見表形式で整理し、実装例とともに解説していきます。早見表を参照しながら読み進めることで、どのような場面でどの設定を使えばよいのか、直感的に理解できるでしょう。

早見表一覧

offset パターン早見表

#パターン名offset 設定説明用途
1要素の登場検知["start end", "end end"]要素の上端が画面下端に到達してから、要素の下端が画面下端に到達するまでフェードイン、スライドイン
2画面中央通過["start center", "end center"]要素の上端が画面中央に到達してから、要素の下端が画面中央に到達するまでスケール変化、回転
3画面全体通過["start start", "end end"]要素の上端が画面上端に到達してから、要素の下端が画面下端に到達するまでパララックス、長尺アニメーション
4固定表示期間["start end", "start start"]要素の上端が画面下端に到達してから、要素の上端が画面上端に到達するまでスティッキーエフェクト
5早期開始["start end", "center end"]要素の上端が画面下端に到達してから、要素の中央が画面下端に到達するまで早めのフェードイン

range パターン早見表

#パターン名range 設定説明効果
1全範囲マッピング[0, 1]スクロール進行度 0%〜100% を値の変化に適用基本的な変化
2前半集中[0, 0.5]スクロール進行度 0%〜50% のみを使用、後半は変化なし早期完了アニメーション
3後半集中[0.5, 1]スクロール進行度 50%〜100% のみを使用、前半は変化なし遅延開始アニメーション
4中間範囲[0.25, 0.75]スクロール進行度 25%〜75% のみを使用中央での変化
5段階的変化[0, 0.3, 0.7, 1]複数の区間で異なる変化を適用複雑なアニメーション

transform マッピング早見表

#プロパティ入力値(range)出力値(transform)説明実装例
1opacity[0, 1][0, 1]透明から不透明へフェードイン
2opacity[0, 1][1, 0]不透明から透明へフェードアウト
3y[0, 1][100, 0]下から上へ移動(px)スライドイン
4scale[0, 1][0.8, 1]小さい状態から通常サイズへズームイン
5scale[0, 1][1, 1.2]通常サイズから拡大ズームアウト
6rotate[0, 1][0, 360]1 回転(度)回転エフェクト
7x[0, 1][-100, 0]左から右へ移動(px)横スライド
8y(パララックス)[0, 1][0, -200]背景を遅く移動パララックス効果

定番組み合わせパターン早見表

#パターン名offsetrangetransform効果
1基本フェードイン["start end", "end end"][0, 1]opacity: [0, 1]要素が画面下から登場時にフェードイン
2スライドフェード["start end", "center center"][0, 1]opacity: [0, 1], y: [50, 0]下から浮き上がりながらフェードイン
3パララックス背景["start start", "end end"][0, 1]y: [0, -300]背景が前景より遅く移動
4ズームフェード["start center", "end center"][0, 1]opacity: [0, 1], scale: [0.8, 1]拡大しながらフェードイン
5スティッキーフェード["start end", "start start"][0, 0.2, 0.8, 1]opacity: [0, 1, 1, 0]表示 → 固定 → 消失

背景

Motion とスクロールアニメーションの重要性

Motion(以前は Framer Motion として知られていました)は、React アプリケーションに宣言的なアニメーションを追加できるライブラリです。従来の CSS アニメーションや JavaScript による手動制御と比べて、コンポーネント指向で直感的な API を提供しています。

スクロールアニメーションは、ユーザーがページをスクロールする動きに連動して要素が変化するエフェクトのことです。適切に使えば、以下のようなメリットがあります。

  • ユーザーの注意を自然に誘導できる
  • 情報の階層構造や流れを視覚的に表現できる
  • インタラクティブな体験を提供し、サイトの印象を向上させる
  • スクロールという自然な操作と連動するため、直感的でわかりやすい

Motion では、useScroll フックを使ってスクロール位置を取得し、useTransform フックで進行度を任意の値に変換することで、スクロール連動アニメーションを実現します。

スクロールアニメーションの基本構造

Motion のスクロールアニメーションは、以下の流れで動作します。

mermaidflowchart TD
  scroll["ユーザーのスクロール操作"] --> useScroll["useScroll フック"]
  useScroll --> progress["進行度<br/>(0〜1の数値)"]
  progress --> offset["offset 設定<br/>(開始・終了位置)"]
  offset --> range["range 設定<br/>(進行度の範囲)"]
  range --> useTransform["useTransform フック"]
  useTransform --> output["出力値<br/>(opacity, y, scale など)"]
  output --> motion["motion コンポーネント"]
  motion --> display["画面表示<br/>(アニメーション適用)"]

この図から分かるように、スクロール操作から画面表示までの間に、複数の設定段階があります。特に offset・range・transform の 3 つの設定が、アニメーションの挙動を決定する重要な要素となっています。

Motion が提供するスクロール関連フック

Motion でスクロールアニメーションを実装する際、主に以下のフックを使用します。

typescriptimport {
  useScroll,
  useTransform,
  useSpring,
} from 'framer-motion';

それぞれのフックの役割は以下の通りです。

  • useScroll: スクロール位置を監視し、進行度を 0〜1 の数値で返す
  • useTransform: 進行度を別の値(opacity、position、scale など)に変換する
  • useSpring: 変換された値にスプリングアニメーション(バネのような動き)を追加する

これらのフックを組み合わせることで、滑らかで魅力的なスクロールアニメーションを実装できます。

課題

offset・range・transform の理解の難しさ

Motion のスクロールアニメーションを実装する際、多くの開発者が最初に直面するのが、offset・range・transform の 3 つのパラメータの理解です。これらは密接に関連しながらも、それぞれ異なる役割を持っています。

offset(オフセット) は、スクロールアニメーションの「開始位置」と「終了位置」を定義します。["start end", "end end"] のような文字列の組み合わせで表現されますが、この表記法が直感的でないため、初心者には分かりづらいという課題があります。

range(レンジ) は、offset で定義された範囲内で、どの進行度をアニメーションに使うかを指定します。[0, 1] のように配列で表現され、スクロール進行度の 0%〜100% を意味します。しかし、[0.25, 0.75] のように部分的な範囲を指定することもでき、この柔軟性が逆に混乱を招くことがあります。

transform(トランスフォーム) は、range で指定された進行度を、実際の CSS プロパティの値に変換します。例えば、進行度 0〜1 を opacity の 0〜1 にマッピングしたり、y 座標の 100〜0px にマッピングしたりします。

パラメータの組み合わせの複雑さ

これら 3 つのパラメータは単独では機能せず、必ず組み合わせて使用する必要があります。この組み合わせの可能性は膨大で、以下のような課題があります。

mermaidflowchart LR
  offsetChoices["offset の選択肢<br/>(開始・終了位置)"] --> combinations["組み合わせ"]
  rangeChoices["range の選択肢<br/>(進行度の範囲)"] --> combinations
  transformChoices["transform の選択肢<br/>(出力値の範囲)"] --> combinations
  combinations --> result["無数のパターン"]
  result --> confusion["どれを選ぶべきか<br/>分からない"]
  • 同じ視覚効果を実現するために、異なるパラメータの組み合わせが存在する
  • 微妙な設定の違いが、大きく異なる結果を生む
  • 公式ドキュメントには基本的な使い方しか載っておらず、実践的なパターンが不足している
  • 試行錯誤に時間がかかり、開発効率が低下する

例えば、「要素が画面に入ったらフェードインさせたい」という単純な要求でも、offset を ["start end", "end end"] にするか、["start center", "end center"] にするかで、アニメーションのタイミングが変わります。さらに、range を全範囲の [0, 1] にするか、前半だけの [0, 0.5] にするかで、変化の速度が変わってきます。

デバッグとトラブルシューティングの難しさ

スクロールアニメーションがうまく動かない場合、以下のような問題が発生することがあります。

TypeError: Cannot read property 'scrollYProgress' of undefined

typescript// エラーが発生するコード例
const { scrollYProgress } = useScroll();
const opacity = useTransform(
  scrollYProgress,
  [0, 1],
  [0, 1]
);

このエラーは、useScroll に ref を渡していない場合や、ref が正しく設定されていない場合に発生します。

アニメーションが想定通りに動かない

  • offset の設定ミスで、アニメーションが全く発動しない
  • range の範囲が狭すぎて、変化が急激になる
  • transform の値の範囲が適切でなく、要素が画面外に飛んでしまう

これらの問題をデバッグするには、スクロール進行度を console.log で出力したり、React Developer Tools でフックの値を監視したりする必要があり、初心者には敷居が高いと言えます。

解決策

早見表による定番パターンの体系化

この記事では、Motion のスクロールアニメーションでよく使われる offset・range・transform の組み合わせを、早見表として体系化しています。記事冒頭の 4 つの早見表を参照することで、以下のメリットが得られます。

  • 時間の節約: 試行錯誤せずに、目的に合ったパターンをすぐに見つけられる
  • 理解の促進: 複数のパターンを比較することで、各パラメータの役割が理解できる
  • 応用の容易さ: 基本パターンを組み合わせることで、独自のアニメーションを作成できる
  • 一貫性の確保: チーム開発で共通のパターンを使うことで、コードの一貫性が保たれる

早見表は、「用途」→「パターン選択」→「実装」という流れで活用できます。例えば、「要素をフェードインさせたい」という用途であれば、「offset パターン早見表」から「要素の登場検知」を選び、「transform マッピング早見表」から「opacity: [0, 1]」を選ぶことで、すぐに実装できるでしょう。

パターンの理解と選択フロー

適切なパターンを選ぶためのフローを図解すると、以下のようになります。

mermaidflowchart TD
  start["アニメーションの<br/>目的を明確にする"] --> timing["タイミングを決める<br/>(いつ開始・終了するか)"]
  timing --> offsetTable["offset パターン早見表<br/>を参照"]
  offsetTable --> speed["変化の速度を決める<br/>(どの範囲で変化するか)"]
  speed --> rangeTable["range パターン早見表<br/>を参照"]
  rangeTable --> property["変化させるプロパティ<br/>を決める"]
  property --> transformTable["transform マッピング<br/>早見表を参照"]
  transformTable --> implement["実装"]
  implement --> test["動作確認"]
  test --> adjust{"調整が<br/>必要?"}
  adjust -->|はい| timing
  adjust -->|いいえ| done["完成"]

このフローに従うことで、迷わず適切なパターンを選択できます。また、動作確認後に調整が必要な場合も、どのパラメータを変更すればよいか判断しやすくなります。

パラメータごとの役割と選び方

それぞれのパラメータの役割と選び方を、改めて整理します。

offset の選び方

offset は「いつアニメーションを開始・終了するか」を決定します。

  • 要素の登場を検知したい: ["start end", "end end"] を使用
  • 画面中央を基準にしたい: ["start center", "end center"] を使用
  • 要素が画面を通過する全期間: ["start start", "end end"] を使用
  • 固定表示期間を作りたい: ["start end", "start start"] を使用

range の選び方

range は「どの進行度をアニメーションに使うか」を決定します。

  • 全体を使って滑らかに変化: [0, 1] を使用
  • 早めに変化を完了させたい: [0, 0.5] を使用
  • 遅れて変化を開始したい: [0.5, 1] を使用
  • 段階的に変化させたい: [0, 0.3, 0.7, 1] のように複数指定

transform の選び方

transform は「進行度をどの CSS プロパティにマッピングするか」を決定します。

  • フェードイン/アウト: opacity: [0, 1] または [1, 0]
  • 上下移動: y: [100, 0] または [0, -100]
  • 左右移動: x: [-100, 0] または [0, 100]
  • 拡大/縮小: scale: [0.8, 1] または [1, 1.2]
  • 回転: rotate: [0, 360]

複数のプロパティを同時に変化させることで、より複雑で魅力的なアニメーションを実現できます。

具体例

ここからは、早見表のパターンを使った具体的な実装例を見ていきます。それぞれのコード例は、実際のプロジェクトでそのまま使える形で紹介します。

基本的なセットアップ

まず、Motion を使ったスクロールアニメーションの基本的なセットアップから始めます。

パッケージのインストール

Motion を使用するには、まずパッケージをインストールします。

bashyarn add framer-motion

必要なインポート

スクロールアニメーションに必要なフックをインポートします。

typescript// React と Motion の必要なフックをインポート
import { useRef } from 'react';
import {
  motion,
  useScroll,
  useTransform,
} from 'framer-motion';

このコードでは、useRef で要素の参照を作成し、motion でアニメーション可能なコンポーネントを作成します。useScrolluseTransform は、スクロール進行度を取得して値を変換するために使用します。

パターン 1: 基本的なフェードインアニメーション

最もシンプルなパターンとして、要素が画面に登場したらフェードインするアニメーションを実装します。このパターンは、早見表の「定番組み合わせパターン」の 1 番を使用します。

コンポーネントの基本構造

typescript// フェードインコンポーネントの基本構造
export const FadeInSection = ({ children }) => {
  // アニメーション対象の要素への参照を作成
  const ref = useRef(null);

  return <div ref={ref}>{children}</div>;
};

まず、useRef を使って、アニメーションを適用したい要素への参照を作成します。この ref は、useScroll フックに渡すことで、特定の要素のスクロール位置を追跡できます。

スクロール進行度の取得

typescript// スクロール進行度を取得する設定
const { scrollYProgress } = useScroll({
  target: ref, // 監視対象の要素
  offset: ['start end', 'end end'], // 要素の上端が画面下端に来たら開始、要素の下端が画面下端で終了
});

useScroll フックは、指定した要素のスクロール進行度を 0〜1 の値で返します。offset パラメータで、アニメーションの開始位置と終了位置を指定しています。["start end", "end end"] は、「要素の上端(start)が画面の下端(end)に到達したら開始し、要素の下端(end)が画面の下端(end)に到達したら終了する」という意味です。

opacity の変換

typescript// 進行度を opacity に変換
const opacity = useTransform(
  scrollYProgress, // 入力値(0〜1のスクロール進行度)
  [0, 1], // 入力範囲(全範囲を使用)
  [0, 1] // 出力範囲(透明から不透明へ)
);

useTransform フックで、スクロール進行度を opacity の値に変換します。進行度が 0(開始位置)のときは opacity が 0(透明)、進行度が 1(終了位置)のときは opacity が 1(不透明)になります。

完成したコンポーネント

typescript// フェードインアニメーションの完成版
export const FadeInSection = ({ children }) => {
  const ref = useRef(null);

  const { scrollYProgress } = useScroll({
    target: ref,
    offset: ['start end', 'end end'],
  });

  const opacity = useTransform(
    scrollYProgress,
    [0, 1],
    [0, 1]
  );

  return (
    <motion.div
      ref={ref}
      style={{ opacity }} // opacity を動的に適用
    >
      {children}
    </motion.div>
  );
};

motion.divstyle={{ opacity }} を指定することで、スクロールに連動して opacity が変化し、フェードインアニメーションが実現されます。

パターン 2: スライドフェードアニメーション

次に、下から浮き上がりながらフェードインするアニメーションを実装します。これは早見表の「定番組み合わせパターン」の 2 番です。

複数プロパティの変換

typescript// opacity と y 座標の両方を変換
const opacity = useTransform(
  scrollYProgress,
  [0, 1], // 全範囲を使用
  [0, 1] // 透明から不透明へ
);

const y = useTransform(
  scrollYProgress,
  [0, 1], // 全範囲を使用
  [50, 0] // 下(50px)から元の位置(0px)へ
);

opacity と y 座標の 2 つのプロパティを同時に変換します。スクロールが進むにつれて、要素が透明 → 不透明になりながら、下から上へ移動します。

完成したスライドフェードコンポーネント

typescript// スライドフェードアニメーションの完成版
export const SlideInSection = ({ children }) => {
  const ref = useRef(null);

  const { scrollYProgress } = useScroll({
    target: ref,
    offset: ['start end', 'center center'], // 画面中央で完了するように調整
  });

  const opacity = useTransform(
    scrollYProgress,
    [0, 1],
    [0, 1]
  );
  const y = useTransform(scrollYProgress, [0, 1], [50, 0]);

  return (
    <motion.div
      ref={ref}
      style={{ opacity, y }} // 複数のプロパティを同時に適用
    >
      {children}
    </motion.div>
  );
};

style={{ opacity, y }} のように、複数のアニメーションプロパティを同時に指定できます。これにより、より豊かな表現が可能になります。

パターン 3: パララックス効果

背景要素を前景要素よりゆっくり動かすパララックス効果を実装します。早見表の「定番組み合わせパターン」の 3 番です。

パララックス用の設定

typescript// パララックス効果のための設定
const { scrollYProgress } = useScroll({
  target: ref,
  offset: ['start start', 'end end'], // 要素が画面を通過する全期間
});

const y = useTransform(
  scrollYProgress,
  [0, 1], // 全範囲を使用
  [0, -300] // 上方向に300px移動(負の値で上方向)
);

パララックス効果では、offset を ["start start", "end end"] に設定することで、要素が画面に入ってから出るまでの全期間でアニメーションを適用します。y の変換範囲を [0, -300] のように大きな値にすることで、ゆっくりとした動きを実現します。

パララックス背景コンポーネント

typescript// パララックス背景の実装
export const ParallaxBackground = ({ imageUrl }) => {
  const ref = useRef(null);

  const { scrollYProgress } = useScroll({
    target: ref,
    offset: ['start start', 'end end'],
  });

  const y = useTransform(
    scrollYProgress,
    [0, 1],
    [0, -300]
  );

  return (
    <div
      ref={ref}
      style={{ position: 'relative', overflow: 'hidden' }}
    >
      <motion.div
        style={{
          y, // y 座標のみを変化させる
          backgroundImage: `url(${imageUrl})`,
          backgroundSize: 'cover',
          backgroundPosition: 'center',
          height: '100vh',
        }}
      />
    </div>
  );
};

親要素に overflow: "hidden" を設定することで、背景が上方向に移動しても、はみ出た部分が隠れるようにしています。

パターン 4: ズームフェードアニメーション

拡大しながらフェードインするアニメーションを実装します。早見表の「定番組み合わせパターン」の 4 番です。

スケールと opacity の組み合わせ

typescript// scale と opacity を組み合わせる
const opacity = useTransform(
  scrollYProgress,
  [0, 1],
  [0, 1] // 透明から不透明へ
);

const scale = useTransform(
  scrollYProgress,
  [0, 1],
  [0.8, 1] // 80%サイズから100%サイズへ
);

scale を 0.8 から 1 に変化させることで、要素が少し小さい状態から通常サイズに拡大するアニメーションになります。0.8 という値は、視覚的に自然な拡大効果を生む定番の値です。

ズームフェードコンポーネント

typescript// ズームフェードアニメーションの完成版
export const ZoomInSection = ({ children }) => {
  const ref = useRef(null);

  const { scrollYProgress } = useScroll({
    target: ref,
    offset: ['start center', 'end center'], // 画面中央を基準
  });

  const opacity = useTransform(
    scrollYProgress,
    [0, 1],
    [0, 1]
  );
  const scale = useTransform(
    scrollYProgress,
    [0, 1],
    [0.8, 1]
  );

  return (
    <motion.div
      ref={ref}
      style={{
        opacity,
        scale, // scale プロパティを追加
      }}
    >
      {children}
    </motion.div>
  );
};

offset を ["start center", "end center"] にすることで、要素が画面中央を通過する間にアニメーションが完了するようになっています。

パターン 5: 複合アニメーション(段階的変化)

最後に、スクロールの進行度に応じて、段階的に異なる効果を適用する複合アニメーションを実装します。

複数の range を使った段階的変化

typescript// 段階的に変化する opacity の設定
const opacity = useTransform(
  scrollYProgress,
  [0, 0.2, 0.8, 1], // 4つの段階を定義
  [0, 1, 1, 0] // 0%でフェードイン、20-80%で表示、100%でフェードアウト
);

range に 4 つの値を指定することで、3 つの段階(0→0.2、0.2→0.8、0.8→1)を作ります。対応する transform の値も 4 つ指定することで、各段階で異なる変化を定義できます。この例では、「フェードイン → 表示維持 → フェードアウト」という流れを実現しています。

複合的な変化の設定

typescript// 複数のプロパティで段階的に変化
const opacity = useTransform(
  scrollYProgress,
  [0, 0.2, 0.8, 1],
  [0, 1, 1, 0] // フェードイン→表示→フェードアウト
);

const scale = useTransform(
  scrollYProgress,
  [0, 0.3, 0.7, 1],
  [0.8, 1, 1, 1.2] // 拡大→通常→さらに拡大
);

const rotate = useTransform(
  scrollYProgress,
  [0, 0.5, 1],
  [0, 0, 10] // 後半で少し回転
);

それぞれのプロパティに異なる段階を設定することで、複雑で印象的なアニメーションを作成できます。

複合アニメーションコンポーネント

typescript// 複合アニメーションの完成版
export const ComplexAnimationSection = ({ children }) => {
  const ref = useRef(null);

  const { scrollYProgress } = useScroll({
    target: ref,
    offset: ['start end', 'start start'], // 固定表示期間を確保
  });

  const opacity = useTransform(
    scrollYProgress,
    [0, 0.2, 0.8, 1],
    [0, 1, 1, 0]
  );

  const scale = useTransform(
    scrollYProgress,
    [0, 0.3, 0.7, 1],
    [0.8, 1, 1, 1.2]
  );

  const rotate = useTransform(
    scrollYProgress,
    [0, 0.5, 1],
    [0, 0, 10]
  );

  return (
    <motion.div
      ref={ref}
      style={{
        opacity,
        scale,
        rotate, // 複数のアニメーションを同時適用
      }}
    >
      {children}
    </motion.div>
  );
};

このコンポーネントは、スクロールに応じて透明度、サイズ、回転が段階的に変化する、ダイナミックなアニメーションを実現します。

使用例とデバッグのヒント

実際にコンポーネントを使用する際のコード例です。

アプリケーションでの使用例

typescript// 実際のページでの使用例
import {
  FadeInSection,
  SlideInSection,
  ParallaxBackground,
} from './animations';

export default function HomePage() {
  return (
    <div>
      {/* パララックス背景 */}
      <ParallaxBackground imageUrl='/hero-background.jpg' />

      {/* フェードインセクション */}
      <FadeInSection>
        <h2>私たちのサービス</h2>
        <p>高品質なWebアプリケーションを提供します。</p>
      </FadeInSection>

      {/* スライドインセクション */}
      <SlideInSection>
        <h2>主な機能</h2>
        <ul>
          <li>高速なパフォーマンス</li>
          <li>直感的なUI/UX</li>
          <li>セキュアな設計</li>
        </ul>
      </SlideInSection>
    </div>
  );
}

コンポーネントを再利用可能な形で作成しておくことで、ページ全体で一貫したアニメーション体験を提供できます。

デバッグ用のプログレス表示

アニメーションが想定通りに動作しない場合は、スクロール進行度を画面に表示してデバッグします。

typescript// デバッグ用のプログレス表示コンポーネント
import { motion, useScroll } from 'framer-motion';
import { useRef } from 'react';

export const DebugSection = ({ children }) => {
  const ref = useRef(null);

  const { scrollYProgress } = useScroll({
    target: ref,
    offset: ['start end', 'end end'],
  });

  return (
    <div ref={ref} style={{ position: 'relative' }}>
      {/* デバッグ用の進行度表示 */}
      <motion.div
        style={{
          position: 'fixed',
          top: 10,
          right: 10,
          padding: '10px',
          background: 'rgba(0,0,0,0.8)',
          color: 'white',
          borderRadius: '5px',
          fontSize: '14px',
        }}
      >
        Progress: {scrollYProgress.get().toFixed(2)}
      </motion.div>

      {children}
    </div>
  );
};

scrollYProgress.get() を使うことで、現在の進行度をリアルタイムで確認できます。これにより、offset や range の設定が適切かどうかを視覚的に確認できます。

よくあるエラーと解決方法

Motion のスクロールアニメーションでよく遭遇するエラーと、その解決方法を紹介します。

Error: useScroll requires a target ref

エラーコード: TypeError: Cannot read property 'current' of null

typescript// エラーが発生するコード
const { scrollYProgress } = useScroll({
  target: ref, // ref が未定義または null
  offset: ['start end', 'end end'],
});

発生条件: useRef で作成した ref が、コンポーネントのレンダリング時に DOM 要素に正しく割り当てられていない場合に発生します。

解決方法:

  1. ref が正しく作成されているか確認する
  2. ref が motion コンポーネントまたは通常の DOM 要素に割り当てられているか確認する
  3. useScroll を呼び出すタイミングを確認する(コンポーネントのトップレベルで呼び出す)
typescript// 正しい実装
export const CorrectComponent = () => {
  const ref = useRef(null); // useRef を正しく初期化

  const { scrollYProgress } = useScroll({
    target: ref,
    offset: ['start end', 'end end'],
  });

  return (
    <motion.div ref={ref}>{/* コンテンツ */}</motion.div>
  ); // ref を正しく割り当て
};

Warning: Invalid offset syntax

エラーコード: Warning: Invalid offset value

typescript// エラーが発生するコード
const { scrollYProgress } = useScroll({
  target: ref,
  offset: ['start', 'end'], // 構文が不正
});

発生条件: offset の構文が正しくない場合に発生します。offset は必ず ["要素の位置 画面の位置", "要素の位置 画面の位置"] という形式で指定する必要があります。

解決方法: offset を正しい形式で記述する

typescript// 正しい offset の形式
const { scrollYProgress } = useScroll({
  target: ref,
  offset: ['start end', 'end end'], // 正しい構文
});

// その他の有効な offset の例
// offset: ["start start", "end end"]
// offset: ["start center", "end center"]
// offset: ["center end", "center start"]

まとめ

この記事では、Motion(旧 Framer Motion)のスクロール進行度マッピングにおける offset・range・transform の定番パターンを、早見表形式で整理して解説しました。

記事の要点を振り返ります。

早見表の活用 記事冒頭の 4 つの早見表(offset パターン、range パターン、transform マッピング、定番組み合わせパターン)を参照することで、目的に合った設定を素早く見つけられます。試行錯誤の時間を大幅に削減でき、開発効率が向上するでしょう。

パラメータの役割 offset は「いつ開始・終了するか」、range は「どの進行度を使うか」、transform は「どの CSS プロパティにマッピングするか」という、それぞれ異なる役割を持っています。この 3 つを適切に組み合わせることで、思い通りのスクロールアニメーションを実現できます。

実装パターン フェードイン、スライドフェード、パララックス、ズームフェード、複合アニメーションという 5 つの代表的なパターンを、実際に動作するコードとともに紹介しました。これらのパターンは、そのまま実務で使えるだけでなく、応用の基礎としても活用できます。

デバッグとエラー対応 スクロール進行度を画面に表示するデバッグ方法や、よくあるエラーとその解決方法を紹介しました。問題が発生した際は、まず進行度を確認し、offset・range・transform のどこに問題があるのかを特定することが重要です。

Motion のスクロールアニメーションは、最初は複雑に感じられるかもしれませんが、基本的なパターンを理解すれば、驚くほど柔軟で強力なツールとなります。早見表を手元に置きながら、ぜひ様々なアニメーションに挑戦してみてください。

ユーザー体験を豊かにするスクロールアニメーションが、あなたのプロジェクトに新しい魅力をもたらすことを願っています。

関連リンク