react-spring-physics

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React Spring Physics

React Spring 物理动画

Physics-based animation for React applications combining React Spring's declarative spring animations with Popmotion's low-level physics utilities.
为React应用打造的基于物理的动画方案,结合了React Spring的声明式弹簧动画与Popmotion的底层物理工具。

Overview

概述

React Spring provides spring-physics animations that feel natural and interruptible. Unlike duration-based animations, springs calculate motion based on physical properties (mass, tension, friction), resulting in organic, realistic movement. Popmotion complements this with composable animation functions for keyframes, decay, and inertia.
When to use this skill:
  • Natural, physics-based UI animations
  • Gesture-driven interfaces (drag, swipe, scroll)
  • Interruptible animations that respond to user input mid-motion
  • Smooth transitions that maintain velocity across state changes
  • Momentum scrolling and inertia effects
Core libraries:
  • @react-spring/web
    - React hooks for spring animations
  • @react-spring/three
    - Three.js integration
  • popmotion
    - Low-level animation utilities (optional, for advanced use cases)
React Spring提供具有自然感且可中断的弹簧物理动画。与基于时长的动画不同,弹簧动画根据物理属性(质量、张力、摩擦力)计算运动轨迹,实现有机、逼真的运动效果。Popmotion则通过可组合的动画函数补充关键帧、衰减和惯性等功能。
适用场景:
  • 自然的基于物理的UI动画
  • 手势驱动界面(拖拽、滑动、滚动)
  • 可中断的动画,能在运动过程中响应用户输入
  • 在状态变化时保持速度的平滑过渡
  • 动量滚动与惯性效果
核心库:
  • @react-spring/web
    - 用于弹簧动画的React钩子
  • @react-spring/three
    - Three.js集成库
  • popmotion
    - 底层动画工具(可选,用于高级场景)

Core Concepts

核心概念

Spring Physics

弹簧物理

Springs animate values from current state to target state using physical simulation:
jsx
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>
}
弹簧动画通过物理模拟将值从当前状态动画过渡到目标状态:
jsx
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,        // 物体重量
      tension: 170,   // 弹簧强度
      friction: 26    // 阻力
    }
  })

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

useSpring Hook Patterns

useSpring钩子使用模式

Two initialization patterns for different use cases:
jsx
// 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 }
  })
}
针对不同场景的两种初始化模式:
jsx
// 对象配置(更简单,属性变化时自动更新)
const springs = useSpring({
  from: { x: 0 },
  to: { x: 100 }
})

// 函数配置(控制度更高,返回用于命令式更新的API)
const [springs, api] = useSpring(() => ({
  from: { x: 0 }
}), [])

// 通过API触发动画
const handleClick = () => {
  api.start({
    from: { x: 0 },
    to: { x: 100 }
  })
}

Spring Configuration Presets

弹簧配置预设

React Spring provides built-in config presets:
jsx
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
})
React Spring提供内置的配置预设:
jsx
import { config } from '@react-spring/web'

// 可用预设
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 }

// 使用示例
const springs = useSpring({
  from: { x: 0 },
  to: { x: 100 },
  config: config.wobbly
})

Common Patterns

常见模式

1. Click-Triggered Spring Animation

1. 点击触发的弹簧动画

jsx
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>
  )
}
jsx
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})`)
      }}
    >
      点击我
    </animated.button>
  )
}

2. Multi-Element Trail Animation

2. 多元素序列动画

jsx
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>
  )
}
jsx
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. List Transitions (Enter/Exit)

3. 列表过渡(进入/退出)

jsx
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>
  ))
}
jsx
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. Scroll-Based Spring Animation

4. 基于滚动的弹簧动画

jsx
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>
  )
}
jsx
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])
      }}
    >
      滚动以显示
    </animated.div>
  )
}

5. Viewport Intersection Animation

5. 视口交叉动画

jsx
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>
}
jsx
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}>内容</animated.div>
}

6. Chained Async Animations

6. 链式异步动画

jsx
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} />
}
jsx
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. Spring with Velocity Preservation

7. 保留速度的弹簧动画

jsx
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} />
}
jsx
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(), // 保留动量
      config: { tension: 200, friction: 20 }
    })
  }

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

Integration Patterns

集成模式

With React Three Fiber (3D)

与React Three Fiber(3D)集成

jsx
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>
  )
}
jsx
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>
  )
}

With Popmotion (Low-Level Physics)

与Popmotion(底层物理)集成

jsx
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)` }} />
}
jsx
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 // 吸附到网格
    }).start(setX)
  }

  return <div style={{ transform: `translateX(${x}px)` }} />
}

With Forms and Validation

与表单和验证集成

jsx
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} />
}
jsx
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} />
}

Performance Optimization

性能优化

On-Demand Rendering

按需渲染

jsx
// Only re-render when animation is active
const [springs, api] = useSpring(() => ({
  from: { x: 0 },
  config: { precision: 0.01 } // Higher value = less updates
}), [])
jsx
// 仅在动画活跃时重新渲染
const [springs, api] = useSpring(() => ({
  from: { x: 0 },
  config: { precision: 0.01 } // 值越高,更新次数越少
}), [])

Batch Multiple Springs

批量处理多个弹簧动画

jsx
// Use useSprings for multiple similar animations
const springs = useSprings(
  items.length,
  items.map(item => ({
    from: { opacity: 0 },
    to: { opacity: 1 }
  }))
)
jsx
// 使用useSprings处理多个相似动画
const springs = useSprings(
  items.length,
  items.map(item => ({
    from: { opacity: 0 },
    to: { opacity: 1 }
  }))
)

Skip Animation (Testing/Accessibility)

跳过动画(测试/无障碍)

jsx
import { Globals } from '@react-spring/web'

// Skip all animations (prefers-reduced-motion)
useEffect(() => {
  Globals.assign({ skipAnimation: true })
  return () => Globals.assign({ skipAnimation: false })
}, [])
jsx
import { Globals } from '@react-spring/web'

// 跳过所有动画(适配减少动画偏好)
useEffect(() => {
  Globals.assign({ skipAnimation: true })
  return () => Globals.assign({ skipAnimation: false })
}, [])

Common Pitfalls

常见陷阱

1. Forgetting Dependencies Array

1. 忘记依赖数组

jsx
// ❌ Wrong: No dependencies, creates new spring every render
const springs = useSpring(() => ({ x: 0 }))

// ✅ Correct: Empty array prevents recreation
const [springs, api] = useSpring(() => ({ x: 0 }), [])
jsx
// ❌ 错误:无依赖数组,每次渲染都会创建新的弹簧
const springs = useSpring(() => ({ x: 0 }))

// ✅ 正确:空数组避免重复创建
const [springs, api] = useSpring(() => ({ x: 0 }), [])

2. Mutating Spring Values

2. 直接修改弹簧值

jsx
// ❌ Wrong: Direct mutation
springs.x.set(100)

// ✅ Correct: Use API to animate
api.start({ x: 100 })
jsx
// ❌ 错误:直接修改
springs.x.set(100)

// ✅ 正确:使用API触发动画
api.start({ x: 100 })

3. Ignoring Config Precision

3. 忽略配置精度

jsx
// ❌ 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
})
jsx
// ❌ 默认精度过高(0.0001),导致不必要的渲染
const springs = useSpring({ x: 0 })

// ✅ 根据场景设置合适的精度
const springs = useSpring({
  x: 0,
  config: { precision: 0.01 } // 当与目标值的差距在0.01内时停止更新
})

4. Not Handling Velocity

4. 未处理速度

jsx
// ❌ Abrupt stop when interrupting animation
api.start({ x: 0 })

// ✅ Preserve momentum
api.start({
  x: 0,
  velocity: springs.x.getVelocity()
})
jsx
// ❌ 中断动画时突然停止
api.start({ x: 0 })

// ✅ 保留动量
api.start({
  x: 0,
  velocity: springs.x.getVelocity()
})

5. Mixing Config Patterns

5. 混合配置模式

jsx
// ❌ 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 }
}), [])
jsx
// ❌ 错误:同时使用对象和函数配置
const springs = useSpring({
  from: { x: 0 }
})
api.start({ x: 100 }) // api未定义

// ✅ 正确:使用函数配置实现命令式控制
const [springs, api] = useSpring(() => ({
  from: { x: 0 }
}), [])

6. Animating Non-Numerical Values

6. 动画非数值型值

jsx
// ❌ 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)`
jsx
// ❌ 错误:弹簧无法直接插值复杂字符串
const springs = useSpring({ transform: 'translateX(100px) rotate(45deg)' })

// ✅ 正确:为单个值设置动画
const springs = useSpring({ x: 100, rotation: 45 })
// 然后组合:transform: `translateX(${x}px) rotate(${rotation}deg)`

Resources

资源

Scripts

脚本

  • spring_generator.py
    - Generate React Spring boilerplate code
  • physics_calculator.py
    - Calculate optimal spring physics parameters
  • spring_generator.py
    - 生成React Spring模板代码
  • physics_calculator.py
    - 计算最优弹簧物理参数

References

参考文档

  • react_spring_api.md
    - Complete React Spring hooks and API reference
  • popmotion_api.md
    - Popmotion functions and reactive streams
  • physics_guide.md
    - Spring physics deep dive with tuning guide
  • react_spring_api.md
    - 完整的React Spring钩子和API参考
  • popmotion_api.md
    - Popmotion函数和响应式流参考
  • physics_guide.md
    - 弹簧物理深度解析与调优指南

Assets

资源包

  • starter_spring/
    - React + Vite template with React Spring examples
  • examples/
    - Real-world patterns (gestures, scroll, 3D integration)
  • starter_spring/
    - 包含React Spring示例的React + Vite模板
  • examples/
    - 真实场景示例(手势、滚动、3D集成)

Related Skills

相关技能

  • motion-framer - Alternative declarative animation approach with variants
  • gsap-scrolltrigger - Timeline-based animations for complex sequences
  • react-three-fiber - 3D scene management (use @react-spring/three for animations)
  • animated-component-libraries - Pre-built animated components using Motion
Physics vs Timeline: Use React Spring for natural, physics-based motion that responds to user input. Use GSAP for precise, timeline-based choreography and complex multi-step sequences.
  • motion-framer - 另一种声明式动画方案,支持变体功能
  • gsap-scrolltrigger - 基于时间轴的动画,适用于复杂序列
  • react-three-fiber - 3D场景管理(搭配@react-spring/three实现动画)
  • animated-component-libraries - 使用Motion构建的预定义动画组件库
物理动画vs时间轴动画:React Spring适用于需要响应用户输入的自然物理运动场景。GSAP则适用于需要精准时间轴编排的复杂多步骤序列动画。