react-spring-physics
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact 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 hooks for spring animations
@react-spring/web - - Three.js integration
@react-spring/three - - Low-level animation utilities (optional, for advanced use cases)
popmotion
React Spring提供具有自然感且可中断的弹簧物理动画。与基于时长的动画不同,弹簧动画根据物理属性(质量、张力、摩擦力)计算运动轨迹,实现有机、逼真的运动效果。Popmotion则通过可组合的动画函数补充关键帧、衰减和惯性等功能。
适用场景:
- 自然的基于物理的UI动画
- 手势驱动界面(拖拽、滑动、滚动)
- 可中断的动画,能在运动过程中响应用户输入
- 在状态变化时保持速度的平滑过渡
- 动量滚动与惯性效果
核心库:
- - 用于弹簧动画的React钩子
@react-spring/web - - Three.js集成库
@react-spring/three - - 底层动画工具(可选,用于高级场景)
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
脚本
- - Generate React Spring boilerplate code
spring_generator.py - - Calculate optimal spring physics parameters
physics_calculator.py
- - 生成React Spring模板代码
spring_generator.py - - 计算最优弹簧物理参数
physics_calculator.py
References
参考文档
- - Complete React Spring hooks and API reference
react_spring_api.md - - Popmotion functions and reactive streams
popmotion_api.md - - Spring physics deep dive with tuning guide
physics_guide.md
- - 完整的React Spring钩子和API参考
react_spring_api.md - - Popmotion函数和响应式流参考
popmotion_api.md - - 弹簧物理深度解析与调优指南
physics_guide.md
Assets
资源包
- - React + Vite template with React Spring examples
starter_spring/ - - Real-world patterns (gestures, scroll, 3D integration)
examples/
- - 包含React Spring示例的React + Vite模板
starter_spring/ - - 真实场景示例(手势、滚动、3D集成)
examples/
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则适用于需要精准时间轴编排的复杂多步骤序列动画。