今回やること
Framer Motionは、Reactで宣言的にアニメーションを実装できるライブラリです。
アニメーションは装飾ではなく、状態変化を理解しやすくするために使うと効果的です。
この記事では次を学びます。
- 基本的なアニメーション
- バリアント
- ジェスチャー
- レイアウトアニメーション
- AnimatePresence
実践メモ: framer-motionはReact専用です。インストール後すぐにmotion.divなどの要素が使えます。
Step 1: セットアップ
# framer-motion 11+
npm install framer-motion
Step 2: 基本的なアニメーション
import { motion } from 'framer-motion';
function BasicAnimation() {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
Hello Animation!
</motion.div>
);
}
Step 3: ホバーとタップ
function InteractiveBox() {
return (
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
className="px-4 py-2 bg-blue-500 text-white rounded"
>
Click me
</motion.button>
);
}
ポイント: initial→animate→transitionの3つのpropsがアニメーションの基本パターンです。
Step 4: バリアント
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
};
function StaggeredList() {
const items = ['Item 1', 'Item 2', 'Item 3'];
return (
<motion.ul
variants={containerVariants}
initial="hidden"
animate="visible"
>
{items.map((item, i) => (
<motion.li key={i} variants={itemVariants}>
{item}
</motion.li>
))}
</motion.ul>
);
}
実践メモ: staggerChildrenを使うと子要素が順番にアニメーションし、リストの表示が格段にリッチになります。
Step 5: AnimatePresence
import { motion, AnimatePresence } from 'framer-motion';
import { useState } from 'react';
function ToggleContent() {
const [isVisible, setIsVisible] = useState(true);
return (
<div>
<button onClick={() => setIsVisible(!isVisible)}>Toggle</button>
<AnimatePresence>
{isVisible && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
>
Content here
</motion.div>
)}
</AnimatePresence>
</div>
);
}
注意: AnimatePresence内の子要素には必ず一意のkeyを付けてください。keyがないとexit animationが動作しません。
Step 6: レイアウトアニメーション
function LayoutAnimation() {
const [selected, setSelected] = useState<number | null>(null);
return (
<div className="grid grid-cols-3 gap-4">
{[1, 2, 3].map((item) => (
<motion.div
key={item}
layout
onClick={() => setSelected(selected === item ? null : item)}
className={`p-4 bg-blue-500 rounded cursor-pointer ${
selected === item ? 'col-span-3' : ''
}`}
>
{item}
</motion.div>
))}
</div>
);
}
ポイント: layoutpropを付けるだけでCSS変更時に自動的にスムーズなアニメーションが適用されます。
Step 7: スクロールアニメーション
import { motion, useScroll, useTransform } from 'framer-motion';
function ScrollAnimation() {
const { scrollYProgress } = useScroll();
const opacity = useTransform(scrollYProgress, [0, 0.5], [1, 0]);
const scale = useTransform(scrollYProgress, [0, 0.5], [1, 0.8]);
return (
<motion.div style={{ opacity, scale }}>
Scroll to see animation
</motion.div>
);
}
Step 8: ドラッグ
function DraggableBox() {
return (
<motion.div
drag
dragConstraints={{ left: 0, right: 300, top: 0, bottom: 300 }}
dragElastic={0.2}
whileDrag={{ scale: 1.1 }}
className="w-20 h-20 bg-blue-500 rounded cursor-grab"
/>
);
}
アニメーションを入れる目的
Framer Motionは簡単にリッチな動きを作れますが、動きが多いほど良いUIになるわけではありません。ユーザーの視線誘導、状態変化の理解、画面遷移の自然さを助けるために使うと効果的です。
特に一覧、モーダル、タブ、エラーメッセージなどは、表示と非表示の変化を少し滑らかにするだけで分かりやすくなります。一方で、常に動き続ける装飾や大きすぎる移動は、操作の邪魔になることがあります。
品質チェック
- 動きが情報理解を助けているか
- prefers-reduced-motionに配慮しているか
- 初回表示でレイアウトが跳ねないか
- スマホで重くならないか
動作確認
アニメーションを入れたら、次を確認します。
| 確認項目 | OKの状態 |
|---|---|
| 表示/非表示 | 開閉時に意図した動きになる |
| key | AnimatePresence の子に一意のkeyがある |
| reduced motion | 動きを減らす設定に配慮している |
| レイアウト | 初回表示でガタつかない |
| スマホ | スクロールやタップを邪魔しない |
動きが多すぎる場合は、時間を短くするより、動かす要素と目的を減らす方が改善しやすいです。
練習
次のUIにアニメーションを追加してください。
- ボタン押下でカードを表示する
- 表示時は opacity と y を変える
- 非表示時は
AnimatePresenceでexitを動かす - 一覧の並び替えに
layoutを付ける - reduced motion時は大きな移動を避ける
次のステップ
UIコンポーネントと組み合わせる場合は shadcn/ui入門 が役立ちます。状態管理が必要な場合は Zustand入門、React Hooksの基礎は React Hooks完全マスター を確認してください。
まとめ
Framer Motionは宣言的なAPIで複雑なアニメーションを簡単に実装できます。バリアントとAnimatePresenceで美しいUIを作りましょう。