2025 年版 Motion(旧 Framer Motion) 徹底活用テクニック集

Motion は単なるアニメーションライブラリを超え、2025 年には Web アプリケーションの UX を革新する強力なツールへと進化しました。
この記事では、Motion v12 の最新機能から高度な実装テクニックまで、実際のプロダクション環境で差をつける実践的な技術を詳しく解説いたします。基礎的な使い方は他の記事にお任せし、ここではプロフェッショナルレベルの実装に挑戦してみましょう。
背景
Motion v12 の革新的変化
2025 年にリリースされた Motion v12 は、パフォーマンスと開発体験の両面で大幅な改善を実現しています。
主要な進化ポイント
機能 | v11 まで | v12 での改善 |
---|---|---|
レンダリング性能 | DOM 操作中心 | Web Workers + GPU 最適化 |
バンドルサイズ | 32KB (gzipped) | 24KB (gzipped) |
TypeScript サポート | 基本的な型定義 | 完全な型安全性 |
React 18 対応 | 限定的 | Concurrent Features 完全対応 |
開発ツール | 基本的なデバッグ | Advanced DevTools 統合 |
特に注目すべきは、Independent Transformの導入により、複雑なアニメーションでも 60FPS を維持できるようになったことです。
現代の Web アプリケーションに求められる高度なアニメーション
現代のユーザーは、ネイティブアプリレベルの滑らかさとレスポンシブ性を Web アプリケーションにも期待しています。
求められる技術水準
- マイクロインタラクション: 0.1 秒以下での即座反応
- 複雑な状態遷移: 多段階のアニメーション連携
- デバイス適応: タッチ、マウス、キーボード操作への最適化
- アクセシビリティ: 前庭障害への配慮と設定対応
課題
複雑なアニメーション実装の技術的ハードル
高度なアニメーションを実装する際に直面する主要な課題を整理しましょう。
状態管理の複雑化 複数のアニメーション要素が相互作用する場合、状態管理が急激に複雑になります。
typescript// 問題のあるアプローチ
const [step1, setStep1] = useState(false);
const [step2, setStep2] = useState(false);
const [step3, setStep3] = useState(false);
const [isReversing, setIsReversing] = useState(false);
// これでは管理が困難
useEffect(() => {
if (step1 && !isReversing) {
setTimeout(() => setStep2(true), 300);
}
if (step2 && !isReversing) {
setTimeout(() => setStep3(true), 600);
}
// さらに複雑になっていく...
}, [step1, step2, step3, isReversing]);
パフォーマンスのボトルネック 不適切な実装により、フレームドロップや応答性の低下が発生します。
パフォーマンスとリッチな表現の両立
美しいアニメーションを実装しつつ、パフォーマンスを維持することは常に課題となります。
メモリリークの回避 長時間動作するアニメーションでのメモリ管理が重要です。
バッテリー消費の最適化 モバイルデバイスでの効率的な動作が求められます。
解決策
2025 年版 Motion 活用戦略
現代的なアプローチで、これらの課題を効率的に解決できます。
状態駆動アーキテクチャ Variants パターンを活用した宣言的なアニメーション管理
パフォーマンスファースト設計 GPU 加速と効率的なレンダリング戦略
型安全性の確保 TypeScript を活用した堅牢な実装
高度なテクニックの体系的習得法
以下の順序で学習することで、効率的にスキルアップできます:
- Variants マスター: 複雑な状態管理の基盤
- Layout Animations: 動的レイアウト変更の制御
- Scroll Integration: スクロール連動の高度活用
- Gesture Handling: タッチ操作の完全制御
- Performance Optimization: 実用レベルの最適化
具体例
Layout Animations の完全攻略法
Layout Animations は Motion の最も強力な機能の一つです。動的なレイアウト変更を自動でアニメーションしてくれます。
基本的な Layout Animation
typescriptimport { motion, AnimatePresence } from 'framer-motion';
import { useState } from 'react';
const AdvancedLayoutExample = () => {
const [selectedId, setSelectedId] = useState<
string | null
>(null);
const [items, setItems] = useState([
{ id: '1', title: 'カード 1', content: 'コンテンツ 1' },
{ id: '2', title: 'カード 2', content: 'コンテンツ 2' },
{ id: '3', title: 'カード 3', content: 'コンテンツ 3' },
]);
return (
<div className='grid-container'>
<motion.div layout className='card-grid'>
{items.map((item) => (
<motion.div
key={item.id}
layoutId={item.id}
className='card'
onClick={() => setSelectedId(item.id)}
whileHover={{ scale: 1.02 }}
transition={{
layout: { duration: 0.3, ease: 'easeOut' },
scale: { duration: 0.2 },
}}
>
<h3>{item.title}</h3>
</motion.div>
))}
</motion.div>
<AnimatePresence>
{selectedId && (
<motion.div
className='overlay'
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => setSelectedId(null)}
>
<motion.div
layoutId={selectedId}
className='expanded-card'
onClick={(e) => e.stopPropagation()}
>
{items.find(
(item) => item.id === selectedId
) && (
<>
<h2>
{
items.find(
(item) => item.id === selectedId
)?.title
}
</h2>
<p>
{
items.find(
(item) => item.id === selectedId
)?.content
}
</p>
<button
onClick={() => setSelectedId(null)}
>
閉じる
</button>
</>
)}
</motion.div>
</motion.div>
)}
</AnimatePresence>
</div>
);
};
高度な Layout 制御テクニック
typescriptimport { motion, LayoutGroup } from 'framer-motion';
const MultiGroupLayout = () => {
const [activeTab, setActiveTab] = useState(0);
return (
<LayoutGroup>
<motion.div className='tab-container'>
{tabs.map((tab, index) => (
<motion.button
key={tab.id}
className={`tab ${
activeTab === index ? 'active' : ''
}`}
onClick={() => setActiveTab(index)}
layout='position'
transition={{
type: 'spring',
stiffness: 300,
damping: 30,
}}
>
{tab.label}
{activeTab === index && (
<motion.div
className='tab-indicator'
layoutId='activeTab'
transition={{ type: 'spring', bounce: 0.3 }}
/>
)}
</motion.button>
))}
</motion.div>
<motion.div
className='tab-content'
layout
transition={{ duration: 0.3 }}
>
<AnimatePresence mode='wait'>
<motion.div
key={activeTab}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.2 }}
>
{tabs[activeTab].content}
</motion.div>
</AnimatePresence>
</motion.div>
</LayoutGroup>
);
};
よくあるエラーと解決法
bash# Error: AnimatePresence children must each have a unique key prop
# 原因: AnimatePresence内の要素にkeyが設定されていない
typescript// 間違った実装
<AnimatePresence>
{showModal && (
<motion.div>モーダル</motion.div> // keyが不足
)}
</AnimatePresence>
// 正しい実装
<AnimatePresence>
{showModal && (
<motion.div key="modal">モーダル</motion.div> // keyを追加
)}
</AnimatePresence>
Scroll-triggered Animations の高度活用
スクロール連動アニメーションの実装で、ユーザーエンゲージメントを大幅に向上させることができます。
useScroll フックの高度活用
typescriptimport {
useScroll,
useTransform,
motion,
} from 'framer-motion';
import { useRef } from 'react';
const AdvancedScrollAnimation = () => {
const containerRef = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({
target: containerRef,
offset: ['start end', 'end start'],
});
// 複数の値を同時にアニメーション
const y = useTransform(
scrollYProgress,
[0, 1],
[100, -100]
);
const opacity = useTransform(
scrollYProgress,
[0, 0.3, 0.7, 1],
[0, 1, 1, 0]
);
const scale = useTransform(
scrollYProgress,
[0, 0.5, 1],
[0.8, 1, 1.2]
);
const rotate = useTransform(
scrollYProgress,
[0, 1],
[0, 360]
);
return (
<div ref={containerRef} className='scroll-container'>
<motion.div
style={{ y, opacity, scale, rotate }}
className='scroll-target'
>
<h2>スクロールで変化する要素</h2>
</motion.div>
</div>
);
};
パララックス効果の実装
typescriptconst ParallaxSection = () => {
const { scrollY } = useScroll();
// 異なる速度でスクロール
const backgroundY = useTransform(
scrollY,
[0, 1000],
[0, -300]
);
const middleY = useTransform(
scrollY,
[0, 1000],
[0, -150]
);
const foregroundY = useTransform(
scrollY,
[0, 1000],
[0, -50]
);
return (
<div className='parallax-container'>
<motion.div
className='parallax-layer background'
style={{ y: backgroundY }}
/>
<motion.div
className='parallax-layer middle'
style={{ y: middleY }}
/>
<motion.div
className='parallax-layer foreground'
style={{ y: foregroundY }}
/>
</div>
);
};
Gesture Integration とカスタムコントロール
タッチジェスチャーとの高度な連携により、ネイティブアプリライクな操作感を実現できます。
bashyarn add framer-motion @use-gesture/react
マルチタッチ対応ドラッグ
typescriptimport {
motion,
useMotionValue,
useTransform,
} from 'framer-motion';
import { useDrag } from '@use-gesture/react';
const AdvancedDragComponent = () => {
const x = useMotionValue(0);
const y = useMotionValue(0);
// ドラッグ距離に基づく視覚効果
const rotateX = useTransform(y, [-200, 200], [30, -30]);
const rotateY = useTransform(x, [-200, 200], [-30, 30]);
const scale = useTransform(
x,
[-200, 0, 200],
[0.8, 1, 0.8]
);
const bind = useDrag({
bounds: {
left: -200,
right: 200,
top: -200,
bottom: 200,
},
rubberband: true,
onDrag: ({
movement: [mx, my],
velocity: [vx, vy],
}) => {
x.set(mx);
y.set(my);
// 速度に基づく追加効果
if (Math.abs(vx) > 500 || Math.abs(vy) > 500) {
// 高速ドラッグ時の特別な処理
console.log('Fast drag detected');
}
},
onDragEnd: ({ velocity: [vx, vy] }) => {
// 慣性に基づく追加アニメーション
const finalX = x.get() + vx * 0.1;
const finalY = y.get() + vy * 0.1;
// 境界チェック付きで最終位置を設定
x.set(Math.max(-200, Math.min(200, finalX)));
y.set(Math.max(-200, Math.min(200, finalY)));
},
});
return (
<motion.div
{...bind()}
style={{
x,
y,
rotateX,
rotateY,
scale,
touchAction: 'none',
}}
className='draggable-advanced'
>
高度なドラッグ操作
</motion.div>
);
};
カスタムジェスチャーの実装
typescriptconst SwipeToActionCard = () => {
const [currentIndex, setCurrentIndex] = useState(0);
const constraintsRef = useRef<HTMLDivElement>(null);
const bind = useDrag({
axis: 'x',
onDragEnd: ({ movement: [mx], velocity: [vx] }) => {
const threshold = 100;
const velocityThreshold = 500;
if (mx > threshold || vx > velocityThreshold) {
// 右スワイプ
setCurrentIndex((prev) => Math.max(0, prev - 1));
} else if (
mx < -threshold ||
vx < -velocityThreshold
) {
// 左スワイプ
setCurrentIndex((prev) =>
Math.min(items.length - 1, prev + 1)
);
}
},
});
return (
<div ref={constraintsRef} className='swipe-container'>
{items.map((item, index) => (
<motion.div
key={item.id}
{...bind()}
className='swipe-card'
animate={{
x: (index - currentIndex) * 300,
scale: index === currentIndex ? 1 : 0.9,
opacity:
Math.abs(index - currentIndex) <= 1 ? 1 : 0.3,
}}
transition={{
type: 'spring',
stiffness: 300,
damping: 30,
}}
>
{item.content}
</motion.div>
))}
</div>
);
};
Variants パターンによる複雑な状態管理
Variants を使うことで、複雑なアニメーション状態を宣言的に管理できます。
typescriptconst complexVariants = {
initial: {
opacity: 0,
scale: 0.8,
y: 50,
},
visible: {
opacity: 1,
scale: 1,
y: 0,
transition: {
duration: 0.6,
ease: 'easeOut',
staggerChildren: 0.1,
},
},
hover: {
scale: 1.05,
transition: { duration: 0.2 },
},
tap: {
scale: 0.95,
transition: { duration: 0.1 },
},
exit: {
opacity: 0,
scale: 0.8,
y: -50,
transition: {
duration: 0.3,
ease: 'easeIn',
},
},
};
const childVariants = {
initial: { opacity: 0, x: -20 },
visible: {
opacity: 1,
x: 0,
transition: { duration: 0.4 },
},
};
const ComplexVariantsExample = () => {
const [isVisible, setIsVisible] = useState(false);
return (
<AnimatePresence>
{isVisible && (
<motion.div
variants={complexVariants}
initial='initial'
animate='visible'
whileHover='hover'
whileTap='tap'
exit='exit'
className='complex-container'
>
<motion.h2 variants={childVariants}>
タイトル
</motion.h2>
<motion.p variants={childVariants}>
説明文
</motion.p>
<motion.button variants={childVariants}>
アクションボタン
</motion.button>
</motion.div>
)}
</AnimatePresence>
);
};
動的 Variants の活用
typescriptconst createDynamicVariants = (itemCount: number) => ({
hidden: {
opacity: 0,
},
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.3,
when: 'beforeChildren',
},
},
});
const itemVariants = {
hidden: {
opacity: 0,
y: 20,
rotateX: -90,
},
visible: {
opacity: 1,
y: 0,
rotateX: 0,
transition: {
type: 'spring',
stiffness: 100,
},
},
};
よくあるエラーと解決法
bash# Error: Variants with name "myVariant" does not exist
# 原因: variantsオブジェクトに存在しない名前を指定
typescript// 問題のあるコード
const variants = {
hidden: { opacity: 0 },
visible: { opacity: 1 }
};
<motion.div
variants={variants}
animate="show" // 存在しないvariant名
/>
// 修正されたコード
<motion.div
variants={variants}
animate="visible" // 正しいvariant名
/>
まとめ
Motion 上級者への道筋
この記事で紹介した高度なテクニックを習得することで、以下のスキルが身につきます:
技術的な成長
- 複雑なアニメーション状態の効率的な管理
- パフォーマンスを意識した実装設計
- TypeScript との組み合わせによる型安全な開発
実践的な応用力
- プロダクション品質のアニメーション実装
- ユーザー体験を大幅に向上させる表現技法
- モダンなフロントエンド開発手法との統合
次のステップとして推奨する学習項目
- React Server Components との組み合わせ
- Next.js App Router での最適化
- WebGL/Three.js との高度な連携
- アクセシビリティを考慮した実装
継続的な実践と実験を通じて、Motion の真の力を引き出してください。2025 年の Web 開発において、このスキルは確実にあなたの強力な武器となるはずです。
関連リンク
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来
- review
人類はなぜ地球を支配できた?『サピエンス全史 上巻』ユヴァル・ノア・ハラリが解き明かす驚愕の真実
- review
え?世界はこんなに良くなってた!『FACTFULNESS』ハンス・ロスリングが暴く 10 の思い込みの正体