mcpField
R
react-spring-physics

react-spring-physics

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

react-spring-physics について

react-spring-physicsは、物理ベースのUIアニメーションを作るReact開発者向けのスキルです。React Springに加え、必要に応じてPopmotionを使い、ジェスチャーや状態変化に応じる中断可能な動きを扱います。

主な機能

  1. 01バネ物理アニメーション
  2. 02ジェスチャー駆動の動きと慣性
  3. 03速度を保つ中断可能なアニメーション
  4. 04シーケンス・リスト・スクロールアニメーション
  5. 05React Three Fiberによる3Dアニメーション

活用シーン

  1. 01ドラッグやスワイプ操作の構築
  2. 02リストの表示・退出アニメーション
  3. 03スクロールに応じたコンテンツ表示
  4. 04Three.jsシーンへのアニメーション追加

インストール

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

$npx skills add https://github.com/freshtechbro/claudedesignskills --skill react-spring-physics

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

同梱ファイル

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

assets/
README.md
references/
physics_guide.md
popmotion_api.md
react_spring_api.md
scripts/
physics_calculator.py
spring_generator.py

詳細

React Spring の物理ベースアニメーション

React アプリケーション向けの物理ベースアニメーションです。React Spring の宣言的なスプリングアニメーションと、Popmotion の低レベル物理ユーティリティを組み合わせています。

概要

React Spring は、自然な動きで中断可能なスプリング物理アニメーションを提供します。時間ベースのアニメーションとは異なり、スプリングは物理特性(質量、張力、摩擦)に基づいて動きを計算するため、有機的でリアルな動きになります。Popmotion は、キーフレーム、減衰、慣性に対応する組み合わせ可能なアニメーション関数でこれを補完します。

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

  • 自然な物理ベースの UI アニメーション
  • ジェスチャー駆動型のインターフェース(ドラッグ、スワイプ、スクロール)
  • 動作中にユーザー入力に応答する、中断可能なアニメーション
  • 状態が変化しても速度を維持する、滑らかなトランジション
  • モメンタムスクロールと慣性効果

主要なライブラリ:

  • @react-spring/web - スプリングアニメーション用の React フック
  • @react-spring/three - Three.js との連携
  • popmotion - 低レベルのアニメーションユーティリティ(任意。高度な用途向け)

主要な概念

スプリング物理

スプリングは物理シミュレーションを使用して、現在の状態から目標の状態へ値をアニメーションさせます。

import { useSpring, animated } from '@react-spring/web'

function SpringExample() {
  const springs = useSpring({
    from: { opacity: 0, y: -40 },
    to: { opacity: 1, y: 0 },
    config: {
      mass: 1,        // Weight of object
      tension: 170,   // Spring strength
      friction: 26    // Opposing force
    }
  })

  return <animated.div style={springs}>Hello</animated.div>
}
useSpring フックのパターン

用途に応じて使い分ける、2 つの初期化パターンです。

// Object config (simpler, auto-updates on prop changes)
const springs = useSpring({
  from: { x: 0 },
  to: { x: 100 }
})

// Function config (more control, returns API for imperative updates)
const [springs, api] = useSpring(() => ({
  from: { x: 0 }
}), [])

// Trigger animation via API
const handleClick = () => {
  api.start({
    from: { x: 0 },
    to: { x: 100 }
  })
}
スプリング設定のプリセット

React Spring には、組み込みの設定プリセットが用意されています。

import { config } from '@react-spring/web'

// Available presets
config.default  // { tension: 170, friction: 26 }
config.gentle   // { tension: 120, friction: 14 }
config.wobbly   // { tension: 180, friction: 12 }
config.stiff    // { tension: 210, friction: 20 }
config.slow     // { tension: 280, friction: 60 }
config.molasses // { tension: 280, friction: 120 }

// Usage
const springs = useSpring({
  from: { x: 0 },
  to: { x: 100 },
  config: config.wobbly
})

よく使われるパターン

1. クリックで開始するスプリングアニメーション
import { useSpring, animated } from '@react-spring/web'

function ClickAnimated() {
  const [springs, api] = useSpring(() => ({
    from: { scale: 1 }
  }), [])

  const handleClick = () => {
    api.start({
      from: { scale: 1 },
      to: { scale: 1.2 },
      config: { tension: 300, friction: 10 }
    })
  }

  return (
    <animated.button
      onClick={handleClick}
      style={{
        transform: springs.scale.to(s => `scale(${s})`)
      }}
    >
      Click Me
    </animated.button>
  )
}
2. 複数要素のトレイルアニメーション
import { useTrail, animated } from '@react-spring/web'

function Trail({ items }) {
  const trails = useTrail(items.length, {
    from: { opacity: 0, x: -20 },
    to: { opacity: 1, x: 0 },
    config: config.gentle
  })

  return (
    <div>
      {trails.map((style, i) => (
        <animated.div key={i} style={style}>
          {items[i]}
        </animated.div>
      ))}
    </div>
  )
}
3. リストのトランジション(表示/終了)
import { useTransition, animated } from '@react-spring/web'

function List({ items }) {
  const transitions = useTransition(items, {
    from: { opacity: 0, height: 0 },
    enter: { opacity: 1, height: 80 },
    leave: { opacity: 0, height: 0 },
    config: config.stiff,
    keys: item => item.id
  })

  return transitions((style, item) => (
    <animated.div style={style}>
      {item.text}
    </animated.div>
  ))
}
4. スクロールベースのスプリングアニメーション
import { useScroll, animated } from '@react-spring/web'

function ScrollReveal() {
  const { scrollYProgress } = useScroll()

  return (
    <animated.div
      style={{
        opacity: scrollYProgress.to([0, 0.5], [0, 1]),
        scale: scrollYProgress.to([0, 0.5], [0.8, 1])
      }}
    >
      Scroll to reveal
    </animated.div>
  )
}
5. ビューポートとの交差時のアニメーション
import { useInView, animated } from '@react-spring/web'

function FadeInOnView() {
  const [ref, springs] = useInView(
    () => ({
      from: { opacity: 0, y: 100 },
      to: { opacity: 1, y: 0 }
    }),
    { rootMargin: '-40% 0%' }
  )

  return <animated.div ref={ref} style={springs}>Content</animated.div>
}
6. 非同期アニメーションの連結
import { useSpring, animated } from '@react-spring/web'

function ChainedAnimation() {
  const springs = useSpring({
    from: { x: 0, background: '#ff6d6d' },
    to: [
      { x: 80, background: '#fff59a' },
      { x: 0, background: '#88DFAB' },
      { x: 80, background: '#569AFF' }
    ],
    config: { tension: 200, friction: 20 },
    loop: true
  })

  return <animated.div style={springs} />
}
7. 速度を維持するスプリング
import { useSpring, animated } from '@react-spring/web'

function VelocityPreservation() {
  const [springs, api] = useSpring(() => ({
    x: 0,
    config: { tension: 300, friction: 30 }
  }), [])

  const handleDragEnd = () => {
    api.start({
      x: 0,
      velocity: springs.x.getVelocity(), // Preserve momentum
      config: { tension: 200, friction: 20 }
    })
  }

  return <animated.div style={springs} onMouseUp={handleDragEnd} />
}

連携パターン

React Three Fiber との連携(3D)
import { useSpring, animated } from '@react-spring/three'
import { Canvas } from '@react-three/fiber'

const AnimatedBox = animated(MeshDistortMaterial)

function ThreeScene() {
  const [clicked, setClicked] = useState(false)

  const springs = useSpring({
    scale: clicked ? 1.5 : 1,
    color: clicked ? '#569AFF' : '#ff6d6d',
    config: { tension: 200, friction: 20 }
  })

  return (
    <Canvas>
      <mesh onClick={() => setClicked(!clicked)} scale={springs.scale}>
        <sphereGeometry args={[1, 64, 32]} />
        <AnimatedBox color={springs.color} />
      </mesh>
    </Canvas>
  )
}
Popmotion との連携(低レベル物理)
import { spring, inertia } from 'popmotion'
import { useState } from 'react'

function PopmotionIntegration() {
  const [x, setX] = useState(0)

  const handleDragEnd = (velocity) => {
    inertia({
      from: x,
      velocity: velocity,
      power: 0.3,
      timeConstant: 400,
      modifyTarget: v => Math.round(v / 100) * 100 // Snap to grid
    }).start(setX)
  }

  return <div style={{ transform: `translateX(${x}px)` }} />
}
フォームとバリデーションとの連携
import { useSpring, animated } from '@react-spring/web'

function ValidatedInput() {
  const [error, setError] = useState(false)

  const shakeAnimation = useSpring({
    x: error ? [0, -10, 10, -10, 10, 0] : 0,
    config: { tension: 300, friction: 10 },
    onRest: () => setError(false)
  })

  return <animated.input style={shakeAnimation} />
}

パフォーマンスの最適化

オンデマンドレンダリング
// Only re-render when animation is active
const [springs, api] = useSpring(() => ({
  from: { x: 0 },
  config: { precision: 0.01 } // Higher value = less updates
}), [])
複数のスプリングをまとめて処理
// Use useSprings for multiple similar animations
const springs = useSprings(
  items.length,
  items.map(item => ({
    from: { opacity: 0 },
    to: { opacity: 1 }
  }))
)
アニメーションをスキップする(テスト/アクセシビリティ)
import { Globals } from '@react-spring/web'

// Skip all animations (prefers-reduced-motion)
useEffect(() => {
  Globals.assign({ skipAnimation: true })
  return () => Globals.assign({ skipAnimation: false })
}, [])

よくある落とし穴

1. 依存配列を忘れる
// ❌ Wrong: No dependencies, creates new spring every render
const springs = useSpring(() => ({ x: 0 }))

// ✅ Correct: Empty array prevents recreation
const [springs, api] = useSpring(() => ({ x: 0 }), [])
2. スプリングの値を変更する
// ❌ Wrong: Direct mutation
springs.x.set(100)

// ✅ Correct: Use API to animate
api.start({ x: 100 })
3. 設定の精度を無視する
// ❌ Default precision too fine (0.0001), causing unnecessary renders
const springs = useSpring({ x: 0 })

// ✅ Set appropriate precision for your use case
const springs = useSpring({
  x: 0,
  config: { precision: 0.01 } // Stop updating when within 0.01 of target
})
4. 速度を処理しない
// ❌ Abrupt stop when interrupting animation
api.start({ x: 0 })

// ✅ Preserve momentum
api.start({
  x: 0,
  velocity: springs.x.getVelocity()
})
5. 設定パターンを混在させる
// ❌ Wrong: Using both object and function config
const springs = useSpring({
  from: { x: 0 }
})
api.start({ x: 100 }) // api is undefined

// ✅ Correct: Use function config for imperative control
const [springs, api] = useSpring(() => ({
  from: { x: 0 }
}), [])
6. 数値以外の値をアニメーションさせる
// ❌ Wrong: Spring can't interpolate complex strings directly
const springs = useSpring({ transform: 'translateX(100px) rotate(45deg)' })

// ✅ Correct: Animate individual values
const springs = useSpring({ x: 100, rotation: 45 })
// Then combine: transform: `translateX(${x}px) rotate(${rotation}deg)`

リソース

スクリプト
  • spring_generator.py - React Spring のボイラープレートコードを生成
  • physics_calculator.py - 最適なスプリング物理パラメーターを計算
リファレンス
  • react_spring_api.md - React Spring のフックと API に関する完全なリファレンス
  • popmotion_api.md - Popmotion の関数とリアクティブストリーム
  • physics_guide.md - 調整ガイド付きのスプリング物理の詳細解説
アセット
  • starter_spring/ - React Spring のサンプルを含む React + Vite テンプレート
  • examples/ - 実践的なパターン(ジェスチャー、スクロール、3D 連携)

関連スキル

  • motion-framer - バリアントを使った、別の宣言的アニメーション手法
  • gsap-scrolltrigger - 複雑なシーケンス向けのタイムラインベースアニメーション
  • react-three-fiber - 3D シーン管理(アニメーションには @react-spring/three を使用)
  • animated-component-libraries - Motion を使用した、すぐに使えるアニメーションコンポーネント

物理ベースとタイムラインベースの使い分け:ユーザー入力に応答する自然な物理ベースの動きには React Spring を使用します。精密なタイムラインベースの演出や、複雑な複数ステップのシーケンスには GSAP を使用します。

コメント