Motion(旧 Framer Motion)useAnimate/useMotionValueEvent 速習チートシート

Motion(旧 Framer Motion)で高度なアニメーションを実装する際、useAnimate
と useMotionValueEvent
は非常に強力なツールです。
この記事では、これら 2 つのフックに焦点を絞り、実践的な使い方を詳しく解説していきます。
複雑なアニメーションシーケンスの制御や、アニメーション値の変化に応じた処理を実装したい方にとって、必須のテクニックとなるでしょう。
速習早見表
useAnimate クイックリファレンス
# | 用途 | 基本構文 | 使用例 |
---|---|---|---|
1 | 基本的なアニメーション | await animate(selector, values, options) | await animate('.box', { x: 100 }, { duration: 0.5 }) |
2 | 連続アニメーション | await で順番に実行 | await animate('.item-1', {...}) → await animate('.item-2', {...}) |
3 | 並列アニメーション | await なしで実行 | animate('.box-1', {...}) と animate('.box-2', {...}) を同時実行 |
4 | 時間差アニメーション | delay オプションを使用 | animate(item, {...}, { delay: index * 0.1 }) |
5 | キーフレーム | 配列で値を指定 | animate('.card', { x: [0, -10, 10, 0] }) |
useMotionValueEvent クイックリファレンス
# | 用途 | 基本構文 | 使用例 |
---|---|---|---|
1 | スクロール進捗監視 | useMotionValueEvent(scrollYProgress, 'change', callback) | スクロール位置に応じた処理実行 |
2 | スクロール量監視 | useMotionValueEvent(scrollY, 'change', callback) | ピクセル単位でのスクロール量取得 |
3 | ドラッグ座標監視 | useMotionValueEvent(x/y, 'change', callback) | ドラッグ中の座標をリアルタイム取得 |
4 | カスタム値監視 | useMotionValueEvent(customValue, 'change', callback) | 任意の MotionValue の変化を監視 |
使い分けガイド
# | シチュエーション | 推奨フック | 実装パターン |
---|---|---|---|
1 | ボタンクリックで順番にアニメーション | useAnimate | await を使った連続実行 |
2 | ページ読み込み時の演出 | useAnimate | useEffect 内で animate を実行 |
3 | スクロール連動の背景色変更 | useMotionValueEvent | scrollY を監視して状態更新 |
4 | ドラッグ操作の座標表示 | useMotionValueEvent | x , y の MotionValue を監視 |
5 | フォーム送信後のアニメーション演出 | useAnimate | 成功/失敗で条件分岐 |
6 | スクロール進捗バー | useMotionValueEvent | scrollYProgress を監視 |
7 | 複数要素の時間差表示 | useAnimate | delay オプションで stagger 実装 |
8 | スクロールトリガーアニメーション | 両方組み合わせ | useMotionValueEvent で監視 + useAnimate で実行 |
よく使うオプション一覧
useAnimate のオプション
# | オプション | 型 | 説明 | デフォルト |
---|---|---|---|---|
1 | duration | number | アニメーション時間(秒) | 0.3 |
2 | delay | number | 遅延時間(秒) | 0 |
3 | ease | string | function | イージング関数 | "easeInOut" |
4 | repeat | number | 繰り返し回数 | 0 |
5 | repeatType | "loop" | "reverse" | "mirror" | 繰り返しの種類 | "loop" |
よく使うイージング
# | イージング名 | 用途 | 視覚的効果 |
---|---|---|---|
1 | "linear" | 一定速度 | 機械的な動き |
2 | "easeIn" | ゆっくり開始 | 加速感 |
3 | "easeOut" | ゆっくり終了 | 減速感(推奨) |
4 | "easeInOut" | 両端がゆっくり | 自然な動き(推奨) |
5 | [0.4, 0, 0.2, 1] | カスタムベジェ曲線 | 細かい調整が可能 |
背景
Motion ライブラリの進化
Motion(旧 Framer Motion)は、React アプリケーションにおけるアニメーション実装の標準的なライブラリとして広く使われています。
基本的な motion
コンポーネントだけでも多くのことが実現できますが、より複雑なアニメーション制御が必要になるケースも増えてきました。
従来の motion
コンポーネントは宣言的な API を提供していますが、命令的な制御やリアルタイムの値監視が必要な場合には限界があります。
そこで登場したのが useAnimate
と useMotionValueEvent
です。
2 つのフックの役割
これらのフックは、それぞれ異なる目的で使用されます。
mermaidflowchart TB
motion["motion コンポーネント<br/>(宣言的 API)"]
useAnimate["useAnimate<br/>(命令的アニメーション制御)"]
useMotionValueEvent["useMotionValueEvent<br/>(値変化の監視)"]
motion -->|より細かい制御が必要| useAnimate
motion -->|値の変化を検知したい| useMotionValueEvent
useAnimate -->|シーケンス制御| seq["複雑なアニメーション<br/>シーケンス"]
useAnimate -->|タイミング制御| timing["細かいタイミング調整"]
useMotionValueEvent -->|イベント処理| event["スクロール連動処理"]
useMotionValueEvent -->|状態同期| sync["他の状態との同期"]
図のポイント:
useAnimate
は命令的にアニメーションを制御する際に使用useMotionValueEvent
は motion の値変化を監視し、リアクティブな処理を実行
課題
宣言的 API の限界
通常の motion
コンポーネントでは、以下のようなケースで実装が困難になります。
# | 課題 | 具体例 |
---|---|---|
1 | 複数要素の連続アニメーション | ボタンクリック後に順番に複数の要素をアニメーション |
2 | 条件分岐を含むアニメーション | ユーザーの操作に応じて異なるアニメーションパスを選択 |
3 | アニメーション値に基づく処理 | スクロール位置に応じて別コンポーネントの状態を変更 |
4 | 動的なタイミング調整 | API レスポンスの内容によってアニメーション速度を変更 |
従来の解決策とその問題点
これまでは以下のような方法で対応していました。
typescript// ❌ 従来の方法:useEffect と animate の組み合わせ
import { motion, useAnimation } from 'framer-motion';
import { useEffect } from 'react';
typescriptfunction OldApproach() {
const controls = useAnimation();
useEffect(() => {
// 複雑な制御が必要な場合、コードが煩雑に
controls
.start({ x: 100 })
.then(() => controls.start({ y: 100 }))
.then(() => controls.start({ opacity: 0 }));
}, [controls]);
return <motion.div animate={controls} />;
}
この方法には以下の問題がありました。
- コンポーネントの ref を取得する必要がある
- 複数要素の制御が煩雑
- TypeScript との相性が悪い
- コードの可読性が低い
解決策
useAnimate による命令的制御
useAnimate
フックは、命令的な方法でアニメーションを制御するための新しい API です。
従来の useAnimation
よりも直感的で、TypeScript のサポートも充実しています。
基本構文
typescriptimport { useAnimate } from 'motion/react';
typescriptfunction BasicExample() {
// scope: アニメーション対象の要素への参照
// animate: アニメーション実行関数
const [scope, animate] = useAnimate();
return <div ref={scope}>アニメーション対象</div>;
}
useAnimate
は 2 つの値を返します。
scope
は対象要素への参照、animate
はアニメーションを実行する関数です。
アニメーション実行の基本形
typescript// 基本的なアニメーション実行
async function runAnimation() {
// 第 1 引数: セレクタ(CSS セレクタまたは要素)
// 第 2 引数: アニメーションの値
// 第 3 引数: オプション(duration, delay など)
await animate('.box', { x: 100 }, { duration: 0.5 });
}
useMotionValueEvent によるイベント処理
useMotionValueEvent
は、motion の値(MotionValue)の変化を監視し、その変化に応じた処理を実行するためのフックです。
スクロール連動やドラッグ操作など、リアルタイムな値の変化に対応できます。
基本構文
typescriptimport {
useMotionValueEvent,
useScroll,
} from 'motion/react';
import { useState } from 'react';
typescriptfunction ScrollProgress() {
const [progress, setProgress] = useState(0);
const { scrollYProgress } = useScroll();
// scrollYProgress の変化を監視
useMotionValueEvent(
scrollYProgress,
'change',
(latest) => {
// latest: 最新の値(0 ~ 1)
setProgress(Math.round(latest * 100));
}
);
return <div>スクロール進捗: {progress}%</div>;
}
第 1 引数に監視対象の MotionValue、第 2 引数にイベント名(通常は 'change'
)、第 3 引数にコールバック関数を指定します。
2 つのフックの使い分け
以下の表を参考に、適切なフックを選択してください。
# | 状況 | 使用するフック | 理由 |
---|---|---|---|
1 | ボタンクリックで複数要素を順番にアニメーション | useAnimate | 命令的なシーケンス制御が必要 |
2 | スクロール位置に応じて背景色を変更 | useMotionValueEvent | 値の変化を継続的に監視 |
3 | フォーム送信後のアニメーション演出 | useAnimate | イベントドリブンな制御 |
4 | ドラッグ中の座標を表示 | useMotionValueEvent | リアルタイムな値の取得 |
具体例
useAnimate の実践例
例 1: 複数要素の連続アニメーション
ボタンをクリックすると、3 つの要素が順番にフェードインするアニメーションです。
typescriptimport { useAnimate } from 'motion/react';
typescriptfunction SequenceAnimation() {
const [scope, animate] = useAnimate();
const handleClick = async () => {
// 初期状態にリセット
await animate(
'.item',
{ opacity: 0, y: 20 },
{ duration: 0 }
);
// 順番にアニメーション(await で待機)
await animate(
'.item-1',
{ opacity: 1, y: 0 },
{ duration: 0.3 }
);
await animate(
'.item-2',
{ opacity: 1, y: 0 },
{ duration: 0.3 }
);
await animate(
'.item-3',
{ opacity: 1, y: 0 },
{ duration: 0.3 }
);
};
return (
<div ref={scope}>
<button onClick={handleClick}>
アニメーション開始
</button>
<div className='item item-1'>要素 1</div>
<div className='item item-2'>要素 2</div>
<div className='item item-3'>要素 3</div>
</div>
);
}
await
を使用することで、各アニメーションの完了を待ってから次のアニメーションを開始できます。
この方法により、複雑なシーケンスも直感的に記述できるのです。
例 2: 並列アニメーションの実行
複数の要素を同時にアニメーションさせる場合は、await
を使用せずに実行します。
typescriptfunction ParallelAnimation() {
const [scope, animate] = useAnimate();
const handleClick = () => {
// 並列実行(await なし)
animate(
'.box-1',
{ x: 100, rotate: 90 },
{ duration: 0.5 }
);
animate(
'.box-2',
{ x: -100, rotate: -90 },
{ duration: 0.5 }
);
animate(
'.box-3',
{ y: 100, scale: 1.5 },
{ duration: 0.5 }
);
};
return (
<div ref={scope}>
<button onClick={handleClick}>
並列アニメーション
</button>
<div className='box-1'>Box 1</div>
<div className='box-2'>Box 2</div>
<div className='box-3'>Box 3</div>
</div>
);
}
例 3: 条件分岐を含むアニメーション
ユーザーの選択に応じて異なるアニメーションを実行する例です。
typescriptfunction ConditionalAnimation() {
const [scope, animate] = useAnimate();
const handleSuccess = async () => {
// 成功時: 緑色にフェードイン後、上にスライドアウト
await animate(
'.card',
{ backgroundColor: '#10b981' },
{ duration: 0.3 }
);
await animate(
'.card',
{ y: -100, opacity: 0 },
{ duration: 0.5 }
);
};
const handleError = async () => {
// エラー時: 赤色に変更後、左右に揺れる
await animate(
'.card',
{ backgroundColor: '#ef4444' },
{ duration: 0.3 }
);
await animate(
'.card',
{ x: [0, -10, 10, -10, 10, 0] },
{ duration: 0.5 }
);
};
return (
<div ref={scope}>
<div className='card'>カード要素</div>
<button onClick={handleSuccess}>成功</button>
<button onClick={handleError}>エラー</button>
</div>
);
}
配列を使用することで、キーフレームアニメーションも簡単に実装できます。
例 4: stagger(時間差)アニメーション
複数要素に時間差をつけてアニメーションする際は、オプションで delay
を指定します。
typescriptfunction StaggerAnimation() {
const [scope, animate] = useAnimate();
const handleClick = async () => {
const items = [
'.item-1',
'.item-2',
'.item-3',
'.item-4',
];
// 各要素に 0.1 秒ずつ遅延を追加
items.forEach((item, index) => {
animate(
item,
{ opacity: 1, x: 0 },
{ duration: 0.5, delay: index * 0.1 }
);
});
};
return (
<div ref={scope}>
<button onClick={handleClick}>スタガー開始</button>
<div
className='item item-1'
style={{
opacity: 0,
transform: 'translateX(-20px)',
}}
>
アイテム 1
</div>
<div
className='item item-2'
style={{
opacity: 0,
transform: 'translateX(-20px)',
}}
>
アイテム 2
</div>
<div
className='item item-3'
style={{
opacity: 0,
transform: 'translateX(-20px)',
}}
>
アイテム 3
</div>
<div
className='item item-4'
style={{
opacity: 0,
transform: 'translateX(-20px)',
}}
>
アイテム 4
</div>
</div>
);
}
useMotionValueEvent の実践例
例 5: スクロール進捗の可視化
ページのスクロール進捗をプログレスバーで表示する例です。
typescriptimport {
useMotionValueEvent,
useScroll,
motion,
} from 'motion/react';
import { useState } from 'react';
typescriptfunction ScrollProgressBar() {
const [progress, setProgress] = useState(0);
const { scrollYProgress } = useScroll();
// scrollYProgress は 0 ~ 1 の値
useMotionValueEvent(
scrollYProgress,
'change',
(latest) => {
setProgress(latest);
}
);
return (
<motion.div
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
height: '5px',
backgroundColor: '#3b82f6',
transformOrigin: '0%',
scaleX: progress,
}}
/>
);
}
scrollYProgress
は画面全体のスクロール進捗を 0 から 1 の値で提供します。
この値をリアルタイムで監視し、プログレスバーの幅に反映させているのです。
例 6: スクロール位置に応じた背景色変更
スクロール位置によって背景色をスムーズに変更する例です。
typescriptfunction ScrollColorChange() {
const [bgColor, setBgColor] = useState('#ffffff');
const { scrollY } = useScroll();
useMotionValueEvent(scrollY, 'change', (latest) => {
// スクロール量に応じて色を変更
if (latest < 300) {
setBgColor('#ffffff'); // 白
} else if (latest < 600) {
setBgColor('#dbeafe'); // 薄い青
} else if (latest < 900) {
setBgColor('#93c5fd'); // 青
} else {
setBgColor('#3b82f6'); // 濃い青
}
});
return (
<div
style={{
backgroundColor: bgColor,
transition: 'background-color 0.5s',
}}
>
{/* ページコンテンツ */}
</div>
);
}
例 7: ドラッグ座標のリアルタイム表示
ドラッグ可能な要素の座標をリアルタイムで表示する例です。
typescriptfunction DragCoordinates() {
const [position, setPosition] = useState({ x: 0, y: 0 });
return (
<>
<motion.div
drag
onUpdate={(latest) => {
// ドラッグ中の座標を取得
if (
latest.x !== undefined &&
latest.y !== undefined
) {
setPosition({
x: Math.round(latest.x),
y: Math.round(latest.y),
});
}
}}
style={{
width: 100,
height: 100,
backgroundColor: '#3b82f6',
borderRadius: 8,
cursor: 'grab',
}}
/>
<div>
X座標: {position.x}px, Y座標: {position.y}px
</div>
</>
);
}
この例では onUpdate
プロパティを使用していますが、useMotionValue
と組み合わせることでより柔軟な制御が可能です。
typescriptimport { motion, useMotionValue } from 'motion/react';
typescriptfunction DragWithMotionValue() {
const [displayX, setDisplayX] = useState(0);
const [displayY, setDisplayY] = useState(0);
const x = useMotionValue(0);
const y = useMotionValue(0);
// X 座標の変化を監視
useMotionValueEvent(x, 'change', (latest) => {
setDisplayX(Math.round(latest));
});
// Y 座標の変化を監視
useMotionValueEvent(y, 'change', (latest) => {
setDisplayY(Math.round(latest));
});
return (
<>
<motion.div
drag
style={{
x,
y,
width: 100,
height: 100,
backgroundColor: '#3b82f6',
}}
/>
<div>
X: {displayX}px, Y: {displayY}px
</div>
</>
);
}
useMotionValue
を使用することで、アニメーション値を直接監視できます。
例 8: スクロール連動型の要素表示制御
スクロール位置に応じて要素の表示・非表示を切り替える例です。
typescriptfunction ScrollReveal() {
const [isVisible, setIsVisible] = useState(false);
const { scrollY } = useScroll();
useMotionValueEvent(scrollY, 'change', (latest) => {
// 300px 以上スクロールしたら表示
setIsVisible(latest > 300);
});
return (
<motion.div
animate={{
opacity: isVisible ? 1 : 0,
y: isVisible ? 0 : 20,
}}
transition={{ duration: 0.5 }}
style={{
position: 'fixed',
bottom: 20,
right: 20,
padding: '10px 20px',
backgroundColor: '#3b82f6',
color: 'white',
borderRadius: 8,
}}
>
トップに戻る
</motion.div>
);
}
組み合わせ例: 高度なインタラクション
useAnimate
と useMotionValueEvent
を組み合わせることで、より高度なインタラクションが実現できます。
例 9: スクロール位置に応じた複雑なアニメーション
typescriptfunction ScrollTriggeredSequence() {
const [scope, animate] = useAnimate();
const { scrollYProgress } = useScroll();
const [hasAnimated, setHasAnimated] = useState(false);
useMotionValueEvent(
scrollYProgress,
'change',
async (latest) => {
// 50% スクロールした時点で一度だけアニメーション実行
if (latest > 0.5 && !hasAnimated) {
setHasAnimated(true);
// 複雑なアニメーションシーケンス
await animate(
'.hero-title',
{ opacity: 1, y: 0 },
{ duration: 0.5 }
);
await animate(
'.hero-subtitle',
{ opacity: 1, y: 0 },
{ duration: 0.5 }
);
// 複数のカードを並列アニメーション
animate(
'.card-1',
{ opacity: 1, scale: 1 },
{ duration: 0.3, delay: 0 }
);
animate(
'.card-2',
{ opacity: 1, scale: 1 },
{ duration: 0.3, delay: 0.1 }
);
animate(
'.card-3',
{ opacity: 1, scale: 1 },
{ duration: 0.3, delay: 0.2 }
);
}
}
);
return (
<div ref={scope}>
<h1
className='hero-title'
style={{
opacity: 0,
transform: 'translateY(20px)',
}}
>
タイトル
</h1>
<p
className='hero-subtitle'
style={{
opacity: 0,
transform: 'translateY(20px)',
}}
>
サブタイトル
</p>
<div
className='card-1'
style={{ opacity: 0, transform: 'scale(0.8)' }}
>
カード 1
</div>
<div
className='card-2'
style={{ opacity: 0, transform: 'scale(0.8)' }}
>
カード 2
</div>
<div
className='card-3'
style={{ opacity: 0, transform: 'scale(0.8)' }}
>
カード 3
</div>
</div>
);
}
この例では、スクロール監視とアニメーション制御を組み合わせることで、ユーザー体験を大きく向上させています。
パフォーマンス最適化のヒント
アニメーションのパフォーマンスを最適化するためのポイントを紹介します。
# | 項目 | 推奨方法 | 理由 |
---|---|---|---|
1 | 変換プロパティ | x , y , scale , rotate を使用 | GPU アクセラレーションが有効 |
2 | 避けるべきプロパティ | width , height , top , left | レイアウト再計算が発生 |
3 | イベント頻度 | 必要最小限の監視に留める | 過度な再レンダリングを防ぐ |
4 | メモ化 | コールバック関数を useCallback でメモ化 | 不要な再生成を防ぐ |
typescriptimport { useCallback } from 'react';
typescriptfunction OptimizedAnimation() {
const [scope, animate] = useAnimate();
const { scrollY } = useScroll();
// コールバックをメモ化
const handleScrollChange = useCallback(
(latest: number) => {
// 100px 単位でのみ処理(頻度を削減)
const threshold = Math.floor(latest / 100) * 100;
if (threshold > 500) {
// 処理を実行
}
},
[]
);
useMotionValueEvent(
scrollY,
'change',
handleScrollChange
);
return <div ref={scope}>{/* コンテンツ */}</div>;
}
まとめ
この記事では、Motion(旧 Framer Motion)の useAnimate
と useMotionValueEvent
について詳しく解説しました。
useAnimate のポイント:
- 命令的にアニメーションを制御できる強力なフック
async/await
で直感的なシーケンス制御が可能- CSS セレクタで複数要素を効率的に制御
- TypeScript との相性が良く、型安全な実装が実現
useMotionValueEvent のポイント:
- MotionValue の変化をリアルタイムで監視
- スクロール連動やドラッグ操作に最適
- React の状態と Motion の値を連携
- パフォーマンスを考慮した実装が重要
これら 2 つのフックを適切に使い分けることで、ユーザー体験を大きく向上させる魅力的なアニメーションが実装できるでしょう。 シンプルな宣言的 API と組み合わせることで、Motion の真の力を引き出せます。
複雑なアニメーションも、これらのツールを使えば驚くほど簡単に実装できますので、ぜひ実際のプロジェクトで試してみてください。
関連リンク
- article
Motion(旧 Framer Motion)useAnimate/useMotionValueEvent 速習チートシート
- article
Motion(旧 Framer Motion)× Vite/Next/Turbopack:ツリーシェイク最適化とバンドル最小化の初期設定
- article
Motion(旧 Framer Motion)でカクつき・ちらつきを潰す:レイアウトシフトと FLIP の落とし穴
- article
Motion(旧 Framer Motion)アーキテクチャ概説:Renderer と Animation Engine を俯瞰する
- article
Motion(旧 Framer Motion)Gesture アニメーション実践:whileHover/whileTap/whileFocus の設計術
- article
Motion(旧 Framer Motion)基本 API 徹底解説:motion 要素・initial/animate/exit の正しい使い方
- article
Motion(旧 Framer Motion)useAnimate/useMotionValueEvent 速習チートシート
- article
Remix ルーティング早見表:ネスト・可変パラメータ・モーダルルート対応一覧
- article
JavaScript IntersectionObserver レシピ集:無限スクロール/遅延読込を最短実装
- article
Preact チートシート【保存版】:JSX/Props/Events/Ref の書き方早見表
- article
Playwright コマンド&テストランナー チートシート【保存版スニペット集】
- article
htmx 属性チートシート:hx-get/hx-post/hx-swap/hx-target 早見表【実例付き】
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来