mcpField
F
framer-motion

framer-motion

github.com/athevonデザイン・UI705989 回インストール3 週間前に更新
リポジトリ

framer-motion について

Claude Code、claude.ai、Cowork 向けのモーションとインタラクション実装スキルです。Motion/Framer Motion の使い方を扱い、依頼がない限り既存プロジェクトのパッケージ名を維持する方針を示します。

主な機能

  1. 01Motion と Framer Motion の API ガイド
  2. 02終了・レイアウトアニメーションのパターン
  3. 03Variants とジェスチャーのガイド
  4. 04バージョン依存 API の注意点

活用シーン

  1. 01条件付き UI に終了アニメーションを追加
  2. 02共有または変化するレイアウトをアニメーション化
  3. 03プロジェクトで使われているパッケージ名を確認

インストール

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

$npx skills add https://github.com/athevon/genjutsu --skill framer-motion

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

詳細

バージョン依存の情報です。 以下のすべての API 名、SDK の要件、ブラウザーサポートに関する記述は、一次情報をもとに 2026-09-08 に確認済みです。何をいつ確認したかは _jutsu/VERSIONS.md をご覧ください。この日付が古い場合は、バージョン番号に基づいて行動する前に再確認してください。

Framer Motion - サブスキル

パッケージ名は 2 つ、ライブラリは 1 つです。 Framer Motion は Motion に改称されました。motion と framer-motion はどちらも同じバージョン(2026-09-08 時点で 13.2.0)を公開しています。motion は "framer-motion": "^13.2.0" を依存関係として宣言し、motion/react はそれを再エクスポートします。どちらも 壊れているわけではなく、インストール済みプロジェクトの大半では、今も framer-motion が使われています。

package.json を読み、そこに記載されているものに従ってください。 ユーザーから依頼がない限り、プロジェクトを どちらか一方からもう一方へ移行しないでください。これは cast の鉄則 8、および paint の鉄則 10 です。

package.json の記載インポート元インストールコマンド
motion"motion/react"npm install motion
framer-motion"framer-motion"npm install framer-motion
両方編集中のファイルですでにインポートされている方を使い、プロジェクトが移行途中であることを伝えてください-

このスキル内の API はすべて、v13 では 2 つの名前で同一です。v11 より新しいシンボルには、インラインで注記しています。ピア依存関係:react / react-dom ^18 || ^19。

Framer Motion と代替手段の使い分け

基準Framer MotionGSAPネイティブ CSS
レイアウトアニメーション優秀(layoutId)手動不可能
終了アニメーションAnimatePresenceタイムラインを逆再生限定的(display)
ジェスチャー(ドラッグ、ホバー)ネイティブ、宣言的Draggable プラグイン基本的
スクロール駆動useScroll + useTransformScrollTrigger(より高機能)scroll-timeline
複雑なオーケストレーションVariants + 伝播タイムライン(より柔軟)@keyframes
バンドルサイズ約 50kb(ツリーシェイキング後)コア約 30kb0kb
React 連携ネイティブ、コンポーネント主体Refs + useGSAPclassName の切り替え

ルール:モーダル、トースト、並べ替え、共有レイアウトなど、React UI のインタラクションには Framer Motion を使います。複雑なタイムライン、映画のようなスクロール駆動アニメーション、SVG のモーフィングには GSAP を使います。

AnimatePresence - 終了アニメーション

<AnimatePresence mode="wait">
  {isVisible && (
    <motion.div
      key="unique-key"        // REQUIRED - identifies the component
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
    />
  )}
</AnimatePresence>
  • mode="wait" - 終了アニメーションの完了後に開始します(ページ遷移)
  • mode="sync" - 終了と開始を同時に行います
  • mode="popLayout" - ただちにフローから削除します(リストに適しています)
  • onExitComplete - すべての終了アニメーションが完了したときに呼び出されるコールバック

レイアウトアニメーション

// Shared layout - the element "slides" between two positions
<motion.div layoutId="highlight" className={activeTab === id ? "active" : ""} />

// Auto layout - animates position/size when layout changes
<motion.div layout>
  {isExpanded && <motion.p layout>Additional content</motion.p>}
</motion.div>

// layout="position" - animates position only (not size)
// layout="size" - animates size only
// layout="preserve-aspect" - preserves the ratio during the transition

Variants - 伝播とオーケストレーション

import { stagger } from "motion/react";

const container = {
  hidden: { opacity: 0 },
  show: {
    opacity: 1,
    transition: {
      // staggerChildren + staggerDirection are DEPRECATED (Motion 12.22, Jul 2025).
      // Pass a stagger() function to delayChildren instead.
      delayChildren: stagger(0.08, { startDelay: 0.2 }),
      // reverse order: stagger(0.08, { from: "last" })
      // also available: from: "first" | "center" | "last" | index, and ease
    },
  },
};

const item = {
  hidden: { opacity: 0, y: 20 },
  show: { opacity: 1, y: 0 },
};

<motion.ul variants={container} initial="hidden" animate="show">
  {items.map((i) => (
    <motion.li key={i.id} variants={item} />
  ))}
</motion.ul>

Variants は motion の子要素に自動的に伝播するため、子要素に initial / animate を指定する必要はありません。

ジェスチャー

<motion.div
  whileHover={{ scale: 1.05 }}
  whileTap={{ scale: 0.95 }}
  whileFocus={{ borderColor: "#3b82f6" }}
  // Drag
  drag               // true = x+y, "x" = horizontal only, "y" = vertical only
  dragConstraints={{ left: -100, right: 100, top: -50, bottom: 50 }}
  dragElastic={0.2}  // 0 = rigid, 1 = free (default 0.35)
  dragSnapToOrigin   // returns to initial position
  onDragEnd={(e, info) => {
    if (info.offset.x > 100) handleSwipe("right");
  }}
/>

Motion values - 再レンダリングなしのリアクティブな更新

const x = useMotionValue(0);
const opacity = useTransform(x, [-200, 0, 200], [0, 1, 0]);
const background = useTransform(x, [-200, 200], ["#ff0000", "#00ff00"]);

// Spring-based smoothing
const smoothX = useSpring(x, { stiffness: 300, damping: 30 });

// Scroll tracking
const { scrollY, scrollYProgress } = useScroll();
const parallaxY = useTransform(scrollYProgress, [0, 1], [0, -300]);

// Element-scoped scroll
const ref = useRef(null);
const { scrollYProgress } = useScroll({
  target: ref,
  offset: ["start end", "end start"],
});

Motion values は React の再レンダリングを引き起こしません。style を通じて DOM を直接更新します。

避けるべきこと

isValidProp なしで styled-components / Emotion と motion を使わない(v13 以降)

Motion 13.0 では、オプションの依存関係だった @emotion/is-prop-valid が削除されました。明示的に注入しないと、motion 専用の props が DOM に漏れてしまいます。

import isPropValid from "@emotion/is-prop-valid";
import { MotionConfig } from "motion/react";

<MotionConfig isValidProp={isPropValid}>
  <App />
</MotionConfig>

または、スタイルライブラリ側で props の受け渡しを制御するよう、合成順を逆にします:const MotionDiv = motion.create(StyledDiv)。

ガードなしでコールバック内の setState を呼び出さない
// BAD - infinite re-render if animate depends on state
onUpdate={(latest) => setPosition(latest.x)}

// GOOD - guard or useMotionValueEvent
const x = useMotionValue(0);
useMotionValueEvent(x, "change", (latest) => {
  if (latest > threshold) onThresholdReached();
});
安定した key なしでレイアウトアニメーションを使わない
// BAD - key changes every render, breaks layout tracking
<motion.div layout key={Math.random()} />

// GOOD - stable key derived from data
<motion.div layout key={item.id} />
AnimatePresence に一意の key を指定し忘れない
// BAD - no key, exit animation does not trigger
<AnimatePresence>
  {isOpen && <motion.div exit={{ opacity: 0 }} />}
</AnimatePresence>

// GOOD - unique key for each conditional child
<AnimatePresence>
  {isOpen && <motion.div key="modal" exit={{ opacity: 0 }} />}
</AnimatePresence>
すでにアニメーション化されているコンポーネントを motion.div でラップしない
// BAD - double animation, transform conflicts
<motion.div animate={{ x: 100 }}>
  <motion.div animate={{ x: -50 }}>Content</motion.div>
</motion.div>

// GOOD - single animation level per transform axis
<motion.div animate={{ x: 100 }}>
  <motion.div animate={{ opacity: 0.5 }}>Content</motion.div>
</motion.div>

// GOOD - use variants to coordinate parent/child
<motion.div variants={parent} animate="active">
  <motion.div variants={child} />
</motion.div>

コメント