mcpField
M
motion-framer

motion-framer

github.com/freshtechbroデザイン・UI1.2k6.1k 回インストール49 週間前に更新
リポジトリ

motion-framer について

motion-framer は、React や JavaScript でアニメーション付き UI を作る開発者向けに、Motion と Framer Motion を解説するスキルです。ジェスチャー、レイアウト・退出アニメーション、スプリング効果、フックや実装例を扱います。

主な機能

  1. 01motion コンポーネントによる宣言的なアニメーション
  2. 02ホバー・タップ・ドラッグ操作への対応
  3. 03レイアウト変更と退出アニメーション
  4. 04スプリング物理とスクロールアニメーション
  5. 05アニメーション用フックとバリアントの解説

活用シーン

  1. 01ボタンやメニューへの操作効果の追加
  2. 02ページ遷移やスクロール演出の作成
  3. 03ドラッグ&ドロップやレイアウト変更の実装
  4. 04状態に応じて動く UI の構築

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/freshtechbro/claudedesignskills --skill motion-framer

インストール後、Claude Code で/motion-framerを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

assets/examples/
README.md
assets/starter_motion/
index.html
package.json
README.md
vite.config.js
assets/starter_motion/src/
App.css
App.jsx
main.jsx
assets/starter_motion/src/components/
DraggableBox.jsx
HoverCard.jsx
StaggerList.jsx
references/
api_reference.md
scripts/
animation_generator.py
variant_builder.py

詳細

Motion と Framer Motion

概要

Motion(旧称 Framer Motion)は、React と JavaScript 向けの本番環境に対応したアニメーションライブラリで、最小限のコードで宣言的かつ高パフォーマンスなアニメーションを実現します。HTML 要素をアニメーション対応にする motion コンポーネントを提供し、ジェスチャー認識(ホバー、タップ、ドラッグ、フォーカス)をサポートするほか、レイアウトアニメーション、終了時のアニメーション、ばね物理などの高度な機能も備えています。

このスキルを使用する場面:

  • インタラクティブな UI コンポーネント(ボタン、カード、メニュー)の構築
  • マイクロインタラクションやホバーエフェクトの作成
  • ページ遷移やルートアニメーションの実装
  • スクロールベースのアニメーションやパララックス効果の追加
  • レイアウト変更(サイズ変更、並べ替え、共有要素の遷移)のアニメーション化
  • ドラッグ&ドロップインターフェイス
  • 複雑なアニメーションシーケンスや状態ベースのアニメーション
  • CSS トランジションを、より強力で制御しやすいアニメーションに置き換える

技術:

  • Motion(v11 以降) - Framer Motion の開発者による、よりモダンで軽量なライブラリ
  • Framer Motion - 機能が充実した旧ライブラリ(現在も広く使用されています)
  • React 18 以降に対応し、Vue もサポート
  • TypeScript に対応
  • Next.js、Vite、Remix、および最新の React フレームワークすべてで動作

基本概念

1. Motion コンポーネント

motion. を付けることで、あらゆる HTML/SVG 要素をアニメーション可能なコンポーネントに変換できます:

import { motion } from "framer-motion"

// Regular HTML becomes motion component
<motion.div />
<motion.button />
<motion.svg />
<motion.path />

すべての motion コンポーネントは、animate、initial、transition などのアニメーション用 props と、whileHover、whileTap などのジェスチャー用 props を受け取ります。

2. Animate Prop

animate prop はアニメーションの目標状態を定義します。値が変わると、Motion は自動的にその状態へアニメーションします:

// Simple animation - x position changes
<motion.div animate={{ x: 100 }} />

// Multiple properties
<motion.div animate={{ x: 100, opacity: 1, scale: 1.2 }} />

// Animates when state changes
const [isOpen, setIsOpen] = useState(false)
<motion.div animate={{ width: isOpen ? 300 : 100 }} />
3. 初期状態

initial prop を使って、アニメーション開始前の初期状態を設定します:

<motion.div
  initial={{ opacity: 0, y: 50 }}
  animate={{ opacity: 1, y: 0 }}
/>

initial={false} を設定すると、マウント時の初期アニメーションを無効にできます。

4. トランジション

transition prop を使って、状態間のアニメーションの動きを制御します:

// Duration-based
<motion.div
  animate={{ x: 100 }}
  transition={{ duration: 0.5, ease: "easeInOut" }}
/>

// Spring physics
<motion.div
  animate={{ scale: 1.2 }}
  transition={{ type: "spring", stiffness: 300, damping: 20 }}
/>

// Different transitions for different properties
<motion.div
  animate={{ x: 100, opacity: 1 }}
  transition={{
    x: { type: "spring", stiffness: 300 },
    opacity: { duration: 0.2 }
  }}
/>

トランジションの種類:

  • "tween"(デフォルト) - イージングを使った時間ベースのアニメーション
  • "spring" - 物理ベースのばねアニメーション
  • "inertia" - 減速するアニメーション(ドラッグで使用)
5. バリアント

名前付きバリアントを使ってアニメーション状態を整理すると、コードがすっきりし、子要素への伝播も可能になります:

const variants = {
  hidden: { opacity: 0, y: 20 },
  visible: { opacity: 1, y: 0 },
  exit: { opacity: 0, scale: 0.9 }
}

<motion.div
  variants={variants}
  initial="hidden"
  animate="visible"
  exit="exit"
/>

バリアントの伝播 - 子要素は親のバリアント状態を自動的に継承します:

const containerVariants = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.1  // Stagger child animations
    }
  }
}

const itemVariants = {
  hidden: { x: -20, opacity: 0 },
  visible: { x: 0, opacity: 1 }
}

<motion.ul variants={containerVariants} initial="hidden" animate="visible">
  <motion.li variants={itemVariants} />
  <motion.li variants={itemVariants} />
  <motion.li variants={itemVariants} />
</motion.ul>

よく使われるパターン

1. ホバーアニメーション

whileHover prop を使って、ホバー時にアニメーションします:

// Simple hover effect
<motion.button
  whileHover={{ scale: 1.1 }}
  transition={{ duration: 0.2 }}
>
  Hover me
</motion.button>

// Multiple properties
<motion.div
  whileHover={{
    scale: 1.05,
    backgroundColor: "#f0f0f0",
    boxShadow: "0px 10px 30px rgba(0, 0, 0, 0.2)"
  }}
>
  Hover card
</motion.div>

// With custom transition
<motion.button
  whileHover={{
    scale: 1.2,
    transition: { duration: 0.1 }  // Transition for gesture start
  }}
  transition={{ duration: 0.5 }}  // Transition for gesture end
>
  Button
</motion.button>

ネストした要素を含むホバー:

<motion.div whileHover="hover" variants={cardVariants}>
  <motion.h3 variants={titleVariants}>Title</motion.h3>
  <motion.img variants={imageVariants} />
</motion.div>
2. タップ/押下時のアニメーション

whileTap prop を使って、タップ/押下時にアニメーションします:

// Scale down on tap
<motion.button
  whileTap={{ scale: 0.9 }}
>
  Click me
</motion.button>

// Combined hover + tap
<motion.button
  whileHover={{ scale: 1.1 }}
  whileTap={{ scale: 0.95, rotate: 3 }}
>
  Interactive button
</motion.button>

// With variants
const buttonVariants = {
  rest: { scale: 1 },
  hover: { scale: 1.1 },
  pressed: { scale: 0.95 }
}

<motion.button
  variants={buttonVariants}
  initial="rest"
  whileHover="hover"
  whileTap="pressed"
>
  Button
</motion.button>
3. ドラッグ操作

drag prop を使って、要素をドラッグ可能にします:

// Basic dragging (both axes)
<motion.div drag />

// Constrain to axis
<motion.div drag="x" />  // Only horizontal
<motion.div drag="y" />  // Only vertical

// Drag constraints
<motion.div
  drag
  dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}
/>

// Drag with parent constraints
<motion.div ref={constraintsRef}>
  <motion.div drag dragConstraints={constraintsRef} />
</motion.div>

// Visual feedback while dragging
<motion.div
  drag
  whileDrag={{
    scale: 1.1,
    boxShadow: "0px 10px 20px rgba(0,0,0,0.2)",
    cursor: "grabbing"
  }}
  dragElastic={0.1}  // Elasticity when dragging outside constraints
  dragTransition={{ bounceStiffness: 600, bounceDamping: 20 }}
/>

ドラッグイベント:

<motion.div
  drag
  onDragStart={(event, info) => console.log(info.point)}
  onDrag={(event, info) => console.log(info.offset)}
  onDragEnd={(event, info) => console.log(info.velocity)}
/>
4. 終了時のアニメーション(AnimatePresence)

AnimatePresence を使うと、コンポーネントが DOM から削除されるときにアニメーションできます:

import { AnimatePresence } from "framer-motion"

// Basic exit animation
<AnimatePresence>
  {isVisible && (
    <motion.div
      key="modal"
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
    />
  )}
</AnimatePresence>

主な要件:

  • コンポーネントは <AnimatePresence> の直接の子要素である必要があります
  • 一意の key prop が必要です
  • 終了時のアニメーションを定義するには exit prop を使用します

終了時のアニメーションを含むリスト項目:

<AnimatePresence>
  {items.map(item => (
    <motion.li
      key={item.id}
      initial={{ opacity: 0, x: -50 }}
      animate={{ opacity: 1, x: 0 }}
      exit={{ opacity: 0, x: 50 }}
      layout  // Smooth layout shifts
    >
      {item.name}
    </motion.li>
  ))}
</AnimatePresence>

順次再生する終了時のアニメーション:

const containerVariants = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      when: "beforeChildren",
      staggerChildren: 0.1
    }
  },
  exit: {
    opacity: 0,
    transition: {
      when: "afterChildren",
      staggerChildren: 0.05,
      staggerDirection: -1  // Reverse order
    }
  }
}

<AnimatePresence>
  {show && (
    <motion.div variants={containerVariants} initial="hidden" animate="visible" exit="exit">
      <motion.div variants={itemVariants} />
      <motion.div variants={itemVariants} />
      <motion.div variants={itemVariants} />
    </motion.div>
  )}
</AnimatePresence>
5. レイアウトアニメーション

layout prop を使うと、レイアウトの変更(位置、サイズ)を自動的にアニメーションできます:

// Animate all layout changes
<motion.div layout />

// Animate only position changes
<motion.div layout="position" />

// Animate only size changes
<motion.div layout="size" />

グリッドレイアウトのアニメーション:

const [columns, setColumns] = useState(3)

<motion.div className="grid">
  {items.map(item => (
    <motion.div
      key={item.id}
      layout
      transition={{ layout: { duration: 0.3, ease: "easeInOut" } }}
    />
  ))}
</motion.div>

共有レイアウトアニメーション(layoutId):

layoutId を使って異なる 2 つの要素をつなぎ、滑らかな遷移を実現します:

// Tab indicator example
<nav>
  {tabs.map(tab => (
    <button key={tab.id} onClick={() => setActive(tab.id)}>
      {tab.label}
      {activeTab === tab.id && (
        <motion.div
          layoutId="underline"
          style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2 }}
        />
      )}
    </button>
  ))}
</nav>

// Modal opening from thumbnail
<motion.img
  src={thumbnail}
  layoutId="product-image"
  onClick={() => setExpanded(true)}
/>

<AnimatePresence>
  {expanded && (
    <motion.div layoutId="product-image">
      <img src={fullsize} />
    </motion.div>
  )}
</AnimatePresence>
6. スクロールベースのアニメーション

whileInView を使うと、要素がビューポートに入ったときにアニメーションできます:

<motion.div
  initial={{ opacity: 0, y: 50 }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, amount: 0.8 }}  // once: trigger once, amount: 80% visible
  transition={{ duration: 0.5 }}
>
  Animates when scrolled into view
</motion.div>

ビューポートのオプション:

  • once: true - アニメーションは一度だけ開始します
  • amount: 0.5 - 表示される要素の割合(0~1)、または "some" | "all"
  • margin: "-100px" - ビューポート境界のオフセット

順次再生するスクロールアニメーション:

<motion.ul
  initial="hidden"
  whileInView="visible"
  viewport={{ once: true, amount: 0.3 }}
  variants={{
    visible: {
      opacity: 1,
      transition: { staggerChildren: 0.1 }
    },
    hidden: { opacity: 0 }
  }}
>
  <motion.li variants={itemVariants} />
  <motion.li variants={itemVariants} />
  <motion.li variants={itemVariants} />
</motion.ul>
7. ばねアニメーション

ばね物理を使って、自然で弾むようなアニメーションを実現します:

// Basic spring
<motion.div
  animate={{ scale: 1.2 }}
  transition={{ type: "spring" }}
/>

// Customize spring physics
<motion.div
  animate={{ x: 100 }}
  transition={{
    type: "spring",
    stiffness: 300,  // Higher = faster, snappier (default: 100)
    damping: 20,     // Higher = less bouncy (default: 10)
    mass: 1,         // Higher = more inertia (default: 1)
  }}
/>

// Visual duration (easier spring control)
<motion.div
  animate={{ rotate: 90 }}
  transition={{
    type: "spring",
    visualDuration: 0.5,  // Perceived duration
    bounce: 0.25          // Bounciness (0-1, default: 0.25)
  }}
/>

ばねのプリセット:

  • Gentle:stiffness: 100, damping: 20
  • Wobbly:stiffness: 200, damping: 10
  • Stiff:stiffness: 400, damping: 30
  • Slow:stiffness: 50, damping: 20

ジェスチャー認識

Motion は宣言的なジェスチャーハンドラーを提供します:

ジェスチャーのプロパティ
<motion.div
  whileHover={{ scale: 1.1 }}        // Pointer hovers over element
  whileTap={{ scale: 0.9 }}          // Primary pointer presses element
  whileFocus={{ outline: "2px" }}    // Element gains focus
  whileDrag={{ scale: 1.1 }}         // Element is being dragged
  whileInView={{ opacity: 1 }}       // Element is in viewport
/>
ジェスチャーイベント
<motion.div
  onHoverStart={(event, info) => {}}
  onHoverEnd={(event, info) => {}}
  onTap={(event, info) => {}}
  onTapStart={(event, info) => {}}
  onTapCancel={(event, info) => {}}
  onDragStart={(event, info) => {}}
  onDrag={(event, info) => {}}
  onDragEnd={(event, info) => {}}
  onViewportEnter={(entry) => {}}
  onViewportLeave={(entry) => {}}
/>

イベント情報オブジェクトには次の内容が含まれます:

  • point: { x, y } - ページ上の座標
  • offset: { x, y } - ドラッグ開始位置からのオフセット
  • velocity: { x, y } - ドラッグの速度

フック

useAnimate

useAnimate フックを使ってアニメーションを手動で制御します:

import { useAnimate } from "framer-motion"

function Component() {
  const [scope, animate] = useAnimate()

  useEffect(() => {
    // Animate multiple elements
    animate([
      [scope.current, { opacity: 1 }],
      ["li", { x: 0, opacity: 1 }, { delay: stagger(0.1) }],
      [".button", { scale: 1.2 }]
    ])
  }, [])

  return (
    <div ref={scope}>
      <ul>
        <li>Item 1</li>
        <li>Item 2</li>
      </ul>
      <button className="button">Click</button>
    </div>
  )
}

アニメーションコントロール:

const controls = animate(element, { x: 100 })
controls.play()
controls.pause()
controls.stop()
controls.speed = 0.5
controls.time = 0  // Seek to start
useSpring

スプリングアニメーションのモーション値を作成します:

import { useSpring } from "framer-motion"

function Component() {
  const x = useSpring(0, { stiffness: 300, damping: 20 })

  return (
    <motion.div style={{ x }}>
      <button onClick={() => x.set(100)}>Move</button>
    </motion.div>
  )
}
useInView

要素がビューポート内にあるかどうかを検出します:

import { useInView } from "framer-motion"

function Component() {
  const ref = useRef(null)
  const isInView = useInView(ref, { once: true, amount: 0.5 })

  return (
    <div ref={ref}>
      {isInView ? "In view!" : "Not in view"}
    </div>
  )
}

統合パターン

GSAP との連携

Motion を状態ベースのアニメーションに、GSAP を複雑なタイムラインに使用します:

import { motion } from "framer-motion"
import gsap from "gsap"

function Component() {
  const boxRef = useRef()

  const handleClick = () => {
    // Use GSAP for complex timeline
    const tl = gsap.timeline()
    tl.to(boxRef.current, { rotation: 360, duration: 1 })
      .to(boxRef.current, { scale: 1.5, duration: 0.5 })
  }

  return (
    // Use Motion for hover/tap/layout animations
    <motion.div
      ref={boxRef}
      whileHover={{ scale: 1.1 }}
      onClick={handleClick}
    />
  )
}
React Three Fiber との連携

Motion の値を使って 3D オブジェクトをアニメーション化します:

import { motion } from "framer-motion"
import { useFrame } from "@react-three/fiber"

function Box() {
  const x = useMotionValue(0)

  useFrame(() => {
    // Sync Motion value with Three.js position
    meshRef.current.position.x = x.get()
  })

  return (
    <>
      <mesh ref={meshRef}>
        <boxGeometry />
        <meshStandardMaterial />
      </mesh>
      <motion.div
        style={{ x }}
        drag="x"
        dragConstraints={{ left: -5, right: 5 }}
      />
    </>
  )
}
フォームライブラリとの連携

フォームの検証状態をアニメーション化します:

import { motion, AnimatePresence } from "framer-motion"

function FormField({ error }) {
  return (
    <div>
      <motion.input
        animate={{
          borderColor: error ? "#ff0000" : "#cccccc",
          x: error ? [0, -10, 10, -10, 10, 0] : 0  // Shake animation
        }}
        transition={{ duration: 0.4 }}
      />
      <AnimatePresence>
        {error && (
          <motion.p
            initial={{ opacity: 0, y: -10 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0, y: -10 }}
            style={{ color: "#ff0000" }}
          >
            {error}
          </motion.p>
        )}
      </AnimatePresence>
    </div>
  )
}

パフォーマンスの最適化

1. transform プロパティを使用する

transform プロパティ(x、y、scale、rotate)はハードウェアアクセラレーションに対応しています:

// ✅ Good - Hardware accelerated
<motion.div animate={{ x: 100, scale: 1.2 }} />

// ❌ Avoid - Triggers layout/paint
<motion.div animate={{ left: 100, width: 200 }} />
2. 個別の transform プロパティ

Motion は、コードを簡潔にするための個別の transform プロパティに対応しています:

// Individual properties (Motion feature)
<motion.div style={{ x: 100, rotate: 45, scale: 1.2 }} />

// Traditional (also supported)
<motion.div style={{ transform: "translateX(100px) rotate(45deg) scale(1.2)" }} />
3. アクセシビリティのためにモーションを減らす

モーションを減らすユーザー設定を尊重します:

import { useReducedMotion } from "framer-motion"

function Component() {
  const shouldReduceMotion = useReducedMotion()

  return (
    <motion.div
      animate={{ x: 100 }}
      transition={shouldReduceMotion ? { duration: 0 } : { duration: 0.5 }}
    />
  )
}
4. レイアウトアニメーションのパフォーマンス

レイアウトアニメーションは処理負荷が高くなることがあります。次の方法で最適化します:

// Specify what to animate
<motion.div layout="position" />  // Only position, not size

// Optimize transition
<motion.div
  layout
  transition={{
    layout: { duration: 0.3, ease: "easeOut" }
  }}
/>
5. layoutId の使用を控える

layoutId は共有レイアウトアニメーションを作成しますが、要素をグローバルに追跡します。必要な場合にのみ使用してください。

よくある落とし穴

1. 終了アニメーションで AnimatePresence を忘れる

問題: 終了アニメーションが機能しません

// ❌ Wrong - No AnimatePresence
{show && <motion.div exit={{ opacity: 0 }} />}
// ✅ Correct - Wrapped in AnimatePresence
<AnimatePresence>
  {show && <motion.div exit={{ opacity: 0 }} />}
</AnimatePresence>
2. リストで key プロパティがない

問題: AnimatePresence が要素を追跡できません

// ❌ Wrong - No key
<AnimatePresence>
  {items.map(item => <motion.div exit={{ opacity: 0 }} />)}
</AnimatePresence>
// ✅ Correct - Unique keys
<AnimatePresence>
  {items.map(item => (
    <motion.div key={item.id} exit={{ opacity: 0 }} />
  ))}
</AnimatePresence>
3. transform 以外のプロパティをアニメーション化する

問題: カクつくアニメーション、低いパフォーマンス

// ❌ Avoid - Not hardware accelerated
<motion.div animate={{ top: 100, left: 50, width: 200 }} />
// ✅ Better - Use transforms
<motion.div animate={{ x: 50, y: 100, scaleX: 2 }} />
4. レイアウトアニメーションの使いすぎ

問題: レイアウトアニメーションを適用した要素が多い場合、パフォーマンスに問題が生じます

// ❌ Too many layout animations
{items.map(item => <motion.div layout>{item}</motion.div>)}
// ✅ Use layout only where needed, optimize others
{items.map(item => (
  <motion.div
    key={item.id}
    animate={{ opacity: 1 }}  // Cheaper animation
    exit={{ opacity: 0 }}
  />
))}
5. 複雑なアニメーションで Variants を使用しない

問題: アニメーションコードが重複し、子要素を連携して制御できません

// ❌ Repetitive
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} />
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} />
// ✅ Use variants
const variants = {
  hidden: { opacity: 0 },
  visible: { opacity: 1 }
}

<motion.div variants={variants} initial="hidden" animate="visible" />
<motion.div variants={variants} initial="hidden" animate="visible" />
6. トランジションのタイミングが不適切

問題: 特定のジェスチャーにトランジションが適用されません

// ❌ Wrong - General transition won't apply to whileHover
<motion.div
  whileHover={{ scale: 1.2 }}
  transition={{ duration: 1 }}  // This applies to animate prop, not whileHover
/>
// ✅ Correct - Transition in whileHover or separate gesture transition
<motion.div
  whileHover={{
    scale: 1.2,
    transition: { duration: 0.2 }  // Applies to hover start
  }}
  transition={{ duration: 0.5 }}  // Applies to hover end
/>

リソース

公式ドキュメント
同梱リソース

このスキルには次の内容が含まれています:

references/

  • api_reference.md - Motion API の完全なリファレンス
  • variants_patterns.md - Variant のパターンとオーケストレーション
  • gesture_guide.md - ジェスチャー処理の包括的なガイド

scripts/

  • animation_generator.py - Motion コンポーネントのボイラープレートを生成
  • variant_builder.py - Variant の設定を行うインタラクティブツール

assets/

  • starter_motion/ - Motion + Vite の完全なスターターテンプレート
  • examples/ - 実際のプロジェクトで使える Motion コンポーネントのパターン
コミュニティリソース

コメント