mcpField
M
motion-design-patterns

motion-design-patterns

github.com/dylanfeltusデザイン・UI180166 回インストール6 週間前に更新
リポジトリ

motion-design-patterns について

React プロジェクト向けに Motion のアニメーションパターンを示すスキルです。画面を構築・改善する開発者向けに、意図の明確さ、性能、タイミングの一貫性を重視した実装例を提供します。

主な機能

  1. 01スプリング設定の参考
  2. 02フェード・上昇と順次表示
  3. 03レイアウトと共有レイアウトのアニメーション
  4. 04ホバーと押下時のインタラクション
  5. 05スクロール連動とページ遷移

活用シーン

  1. 01カードグリッドに表示・ホバー効果を追加
  2. 02タブ切替やアコーディオン展開をアニメーション化
  3. 03ランディングページにスクロール連動効果を追加

インストール

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

$npx skills add https://github.com/dylanfeltus/skills --skill motion-design-patterns

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

同梱ファイルREADME.md

詳細

モーションデザインパターン

React 向けの Framer Motion(Motion)パターンです。スプリング、スタガー、レイアウトアニメーション、マイクロインタラクション、スクロール連動エフェクト、終了アニメーションを扱います。ありきたりな UI と洗練された UI を分ける最大の要素です。

使用する場面

  • React プロジェクトの UI アニメーションを作成または改善する場合
  • ユーザーが「洗練させる」「使っていて楽しくする」「マイクロインタラクション」「使い心地をよくする」と依頼した場合
  • 表示時/終了時のアニメーション、ホバーエフェクト、ページ遷移を追加する場合
  • リスト、カード、モーダル、ナビゲーションを上質な印象にしたい場合
  • ユーザーが Magic UI、Motion Primitives、Framer Motion に言及した場合

基本理念

  • モーションには目的を持たせます。 すべてのアニメーションは、状態の変化、階層、空間的な関係、フィードバックなどを伝える必要があります。
  • 控えめにするほど効果的です。 適切に調整したスプリングを 1 つ使うほうが、互いに競合するアニメーションを 5 つ使うより優れています。
  • パフォーマンスを最優先します。 アニメーションは transform と opacity のみにします。width、height、top、left、margin はアニメーションさせないでください。
  • 一貫性が重要です。 プロジェクト全体で同じスプリング設定を使います。

依存関係

npm install motion

インポート:import { motion, AnimatePresence, stagger, useScroll, useTransform } from "motion/react"

注:パッケージ名は 2024 年後半に framer-motion から motion に変更されました。どちらも動作しますが、現在のパッケージは motion です。


スプリング設定

スプリングはイージング曲線よりも自然に感じられます。デフォルトとして以下を使用してください。

推奨デフォルト
// Snappy — buttons, toggles, small elements
const snappy = { type: "spring", stiffness: 500, damping: 30 }

// Smooth — cards, panels, modals
const smooth = { type: "spring", stiffness: 300, damping: 25 }

// Gentle — page transitions, large elements
const gentle = { type: "spring", stiffness: 200, damping: 20 }

// Bouncy — playful UI, notifications, badges
const bouncy = { type: "spring", stiffness: 400, damping: 15 }
クイックリファレンス
印象stiffnessdamping用途
キビキビ50030ボタン、トグル、チップ
滑らか30025カード、パネル、モーダル
穏やか20020ページ遷移、ヒーロー
弾む40015通知、バッジ、楽しい UI
目安
  • stiffness を高くする = アニメーションが速くなります
  • damping を低くする = より弾みます
  • damping ratio < 1 = オーバーシュート(バウンド)します
  • バウンドさせない場合は、damping を 2 × √stiffness 以上に設定します

パターン 1:フェード+上昇表示

定番の表示アニメーションです。要素が少し上にスライドしながらフェードインします。

<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ type: "spring", stiffness: 300, damping: 25 }}
>
  Content here
</motion.div>

使用する場面: カード、セクション、ページに表示されるあらゆるコンテンツ。

アンチパターン: y: 100 など大きな値は使わないでください。控えめな値(12~24px)は上質に感じられますが、大きな値はぎこちなく見えます。


パターン 2:スタガー付きリスト

子要素を 1 つずつアニメーションさせます。リストに生き生きとした印象を与える連鎖効果です。

const container = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.08,
      delayChildren: 0.1,
    },
  },
}

const item = {
  hidden: { opacity: 0, y: 16 },
  visible: {
    opacity: 1,
    y: 0,
    transition: { type: "spring", stiffness: 300, damping: 25 },
  },
}

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

タイミングの目安:

  • 3~5 個の項目:staggerChildren: 0.1
  • 6~12 個の項目:staggerChildren: 0.06
  • 12 個以上:staggerChildren: 0.03(またはグループとしてアニメーション)

アンチパターン: 個別にスタガーさせる項目を約 15 個以上にしないでください。遅く感じられます。グループ化するか、波状のエフェクトを使います。


パターン 3:レイアウトアニメーション

レイアウト状態間を自動的にアニメーションさせます。Motion の最大の特長です。

<motion.div layout transition={{ type: "spring", stiffness: 300, damping: 25 }}>
  {isExpanded ? <ExpandedContent /> : <CollapsedContent />}
</motion.div>
共有レイアウト(タブ、アクティブインジケーター)
{tabs.map((tab) => (
  <button key={tab.id} onClick={() => setActive(tab.id)}>
    {tab.label}
    {active === tab.id && (
      <motion.div
        layoutId="activeTab"
        className="absolute inset-0 bg-primary rounded-md"
        transition={{ type: "spring", stiffness: 400, damping: 30 }}
      />
    )}
  </button>
))}

使用する場面: タブインジケーター、展開するカード、並べ替え可能なリスト、フィルタリングするグリッド。

パフォーマンスのヒント: 位置のみ(サイズは対象外)をアニメーションさせる場合は、layout="position" を追加します。こちらのほうが処理負荷を抑えられます。


パターン 4:終了アニメーション(AnimatePresence)

DOM から削除される前に、要素をアニメーションで退出させます。

<AnimatePresence mode="wait">
  {isVisible && (
    <motion.div
      key="modal"
      initial={{ opacity: 0, scale: 0.95 }}
      animate={{ opacity: 1, scale: 1 }}
      exit={{ opacity: 0, scale: 0.95 }}
      transition={{ type: "spring", stiffness: 300, damping: 25 }}
    >
      Modal content
    </motion.div>
  )}
</AnimatePresence>
AnimatePresence のモード
モード動作用途
"sync"(デフォルト)表示と終了を同時に行いますクロスフェードエフェクト
"wait"表示を開始する前に終了の完了を待ちますページ遷移、モーダル
"popLayout"終了する要素をレイアウトフローから取り除きます項目を削除するリスト

パターン 5:ホバーとタップのマイクロインタラクション

<motion.button
  whileHover={{ scale: 1.02 }}
  whileTap={{ scale: 0.98 }}
  transition={{ type: "spring", stiffness: 500, damping: 30 }}
>
  Click me
</motion.button>
要素タイプ別のホバーパターン
要素whileHoverwhileTap
ボタン(プライマリ){ scale: 1.02 }{ scale: 0.98 }
カード{ y: -4, boxShadow: "0 8px 30px rgba(0,0,0,0.12)" }—
アイコンボタン{ scale: 1.1 }{ scale: 0.9 }
リンク{ x: 2 }—
アバター{ scale: 1.05 }—

アンチパターン: ボタンのスケールを 1.05 より大きくしないでください。漫画のような印象になります。控えめな値(1.01~1.03)のほうが上質に感じられます。


パターン 6:スクロールでトリガーされるアニメーション

ビューに入るときのアニメーション
<motion.div
  initial={{ opacity: 0, y: 40 }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, margin: "-100px" }}
  transition={{ type: "spring", stiffness: 200, damping: 20 }}
>
  Appears when scrolled into view
</motion.div>

viewport.once: true — 最初の一度だけアニメーションします(ランディングページで最も一般的です)。 viewport.margin — 負のマージンを指定すると、要素が完全に表示される前にトリガーされます。

スクロールに連動する進行状況
const { scrollYProgress } = useScroll()
const opacity = useTransform(scrollYProgress, [0, 0.3], [1, 0])
const y = useTransform(scrollYProgress, [0, 0.3], [0, -50])

<motion.div style={{ opacity, y }}>
  Parallax hero content
</motion.div>

パターン 7:ページ遷移

// In your layout or page wrapper
<AnimatePresence mode="wait">
  <motion.main
    key={pathname}
    initial={{ opacity: 0, y: 8 }}
    animate={{ opacity: 1, y: 0 }}
    exit={{ opacity: 0, y: -8 }}
    transition={{ type: "spring", stiffness: 300, damping: 30 }}
  >
    {children}
  </motion.main>
</AnimatePresence>

控えめにしましょう。 ページ遷移は短く(体感で 200~300ms)、動きも小さく(8~12px)します。派手なページ遷移は、2015 年のような印象を与えます。


パターン 8:数値/テキストの遷移

// Animate a counter
<motion.span
  key={count}
  initial={{ opacity: 0, y: -10 }}
  animate={{ opacity: 1, y: 0 }}
  exit={{ opacity: 0, y: 10 }}
>
  {count}
</motion.span>

退出アニメーションのために、AnimatePresence で囲みます。ダッシュボード、料金表示、ライブデータに最適です。


避けるべきアンチパターン

❌ してはいけないこと✅ 代わりにすること
width/height を直接アニメーションさせるscale または layout アニメーションを使う
大きな移動量(y: 200)控えめな値(y: 16–24)
何にでもバウンスを使う遊び心や祝福を表現する場面に限ってバウンスを使う
スクロールイベントのたびにアニメーションさせるonce: true を指定して whileInView を使う
要素ごとにタイミングを変えるプロジェクト全体で一貫したスプリング設定を使う
ページ読み込み時にすべてをアニメーションさせるファーストビューを優先し、残りは順番に表示する
カスタムのイージングカーブスプリングを使う — 割り込みにうまく応答します

おすすめのコンポーネントライブラリの参考情報

アニメーション付きコンポーネントを作成する際は、パターンやアイデアの参考として以下を確認してください。

  • Magic UI — アニメーション付き React コンポーネントを 150 種類以上収録。shadcn/ui に対応
  • Motion Primitives — React 用のモーションコンポーネントをコピーして貼り付けて使えます
  • Aceternity UI — トレンド感のあるアニメーション付きコンポーネント(より重量感があり、演出も強め)

ユーザーが特定のアニメーション付きコンポーネント(テキストの表示、アニメーション付きボーダー、グラデーションアニメーションなど)を求めている場合は、まずこれらのライブラリを確認してください。実績のある実装が見つかる可能性があります。


選択のクイックガイド

シナリオパターンスプリング設定
カードの表示フェード+上昇smooth
リストの読み込み順次表示するリストsmooth、0.08s の間隔
タブの切り替え共有レイアウト(layoutId)snappy
モーダルの開閉AnimatePresence+スケールsmooth
ボタンの押下whileHover+whileTapsnappy
ランディングページのセクションスクロールでトリガーgentle
ページ移動ページ遷移smooth
ダッシュボードのカウンター数値の遷移snappy
通知ポップアップフェード+上昇+バウンスbouncy
アコーディオンの展開レイアウトアニメーションsmooth

例

例 1:「このカードグリッドにアニメーションを追加する」

グリッドにフェード+上昇の順次表示を適用し、各カードにホバー時の浮き上がり効果を追加します。

<motion.div
  variants={container}
  initial="hidden"
  animate="visible"
  className="grid grid-cols-3 gap-4"
>
  {cards.map((card) => (
    <motion.div
      key={card.id}
      variants={item}
      whileHover={{ y: -4, boxShadow: "0 8px 30px rgba(0,0,0,0.12)" }}
      transition={{ type: "spring", stiffness: 300, damping: 25 }}
    >
      <Card {...card} />
    </motion.div>
  ))}
</motion.div>
例 2:「このモーダルの見栄えをよくする」

AnimatePresence で囲み、表示/退出時にスケール+不透明度のアニメーションを追加し、オーバーレイをフェードさせます。

<AnimatePresence>
  {isOpen && (
    <>
      <motion.div
        className="fixed inset-0 bg-black/50"
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
      />
      <motion.div
        className="fixed inset-0 flex items-center justify-center"
        initial={{ opacity: 0, scale: 0.95 }}
        animate={{ opacity: 1, scale: 1 }}
        exit={{ opacity: 0, scale: 0.95 }}
        transition={{ type: "spring", stiffness: 300, damping: 25 }}
      >
        <ModalContent onClose={onClose} />
      </motion.div>
    </>
  )}
</AnimatePresence>
例 3:「このランディングページにスクロールアニメーションを追加する」

各セクションに、子要素を順次表示する whileInView を適用します。

<motion.section
  initial={{ opacity: 0, y: 40 }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, margin: "-100px" }}
  transition={{ type: "spring", stiffness: 200, damping: 20 }}
>
  <h2>Feature Section</h2>
  <motion.div
    variants={container}
    initial="hidden"
    whileInView="visible"
    viewport={{ once: true }}
  >
    {features.map((f) => (
      <motion.div key={f.id} variants={item}>
        <FeatureCard {...f} />
      </motion.div>
    ))}
  </motion.div>
</motion.section>

コメント