
React Spring Physics
- 1.4k installs
- 629 repo stars
- Updated November 20, 2025
- freshtechbro/claudedesignskills
react-spring-physics provides documented workflows for Physics-based animation library combining React Spring (spring dynamics, gesture integration, 60fps animations) and Popmotion (low-level composable animation ut
About
The react-spring-physics skill physics-based animation library combining React Spring spring dynamics gesture integration 60fps animations and Popmotion low-level composable animation utilities reactive streams Use when building fluid natural-feeling UI animations gesture-driven interfaces physics simulations or spring-loaded interactions Triggers on tasks involving React Spring hooks spring physics inertia scrolling physics-based motion animation composition or natural UI movements Alternative physics appr React Spring Physics Physics-based animation for React applications combining React Spring's declarative spring animations with Popmotion's low-level physics utilities 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 transiti.
- 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
React Spring Physics by the numbers
- 1,410 all-time installs (skills.sh)
- +97 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #327 of 2,203 Security skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
react-spring-physics capabilities & compatibility
- Capabilities
- natural, physics based ui animations · gesture driven interfaces (drag, swipe, scroll) · interruptible animations that respond to user in · smooth transitions that maintain velocity across · momentum scrolling and inertia effects
- Use cases
- documentation
What react-spring-physics says it does
# React Spring Physics Physics-based animation for React applications combining React Spring's declarative spring animations with Popmotion's low-level physics utilities.
## Overview React Spring provides spring-physics animations that feel natural and interruptible.
npx skills add https://github.com/freshtechbro/claudedesignskills --skill react-spring-physicsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.4k |
|---|---|
| repo stars | ★ 629 |
| Security audit | 3 / 3 scanners passed |
| Last updated | November 20, 2025 |
| Repository | freshtechbro/claudedesignskills ↗ |
How do I use react-spring-physics for the task described in its SKILL.md triggers?
Physics-based animation library combining React Spring (spring dynamics, gesture integration, 60fps animations) and Popmotion (low-level composable animation utilities, reactive streams). Use when bu.
Who is it for?
Teams invoking react-spring-physics when the user request matches documented triggers and prerequisites.
Skip if: Skip when cached docs are missing, the request is a negative trigger, or another sibling skill owns the workflow.
When should I use this skill?
Physics-based animation library combining React Spring (spring dynamics, gesture integration, 60fps animations) and Popmotion (low-level composable animation utilities, reactive streams). Use when building fluid, natural
What you get
Step-by-step guidance grounded in react-spring-physics documentation and reference files.
- Vite React project with React Spring
- Animation starter patterns
Files
React Spring Physics
Physics-based animation for React applications combining React Spring's declarative spring animations with Popmotion's low-level physics utilities.
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 integrationpopmotion- Low-level animation utilities (optional, for advanced use cases)
Core Concepts
Spring Physics
Springs animate values from current state to target state using physical simulation:
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 Hook Patterns
Two initialization patterns for different use cases:
// 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 }
})
}Spring Configuration Presets
React Spring provides built-in config presets:
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
})Common Patterns
1. Click-Triggered Spring Animation
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. Multi-Element Trail Animation
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)
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
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. Viewport Intersection Animation
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. Chained Async Animations
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
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} />
}Integration Patterns
With 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>
)
}With Popmotion (Low-Level Physics)
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)` }} />
}With Forms and Validation
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
// Only re-render when animation is active
const [springs, api] = useSpring(() => ({
from: { x: 0 },
config: { precision: 0.01 } // Higher value = less updates
}), [])Batch Multiple Springs
// Use useSprings for multiple similar animations
const springs = useSprings(
items.length,
items.map(item => ({
from: { opacity: 0 },
to: { opacity: 1 }
}))
)Skip Animation (Testing/Accessibility)
import { Globals } from '@react-spring/web'
// Skip all animations (prefers-reduced-motion)
useEffect(() => {
Globals.assign({ skipAnimation: true })
return () => Globals.assign({ skipAnimation: false })
}, [])Common Pitfalls
1. Forgetting Dependencies Array
// ❌ 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. Mutating Spring Values
// ❌ Wrong: Direct mutation
springs.x.set(100)
// ✅ Correct: Use API to animate
api.start({ x: 100 })3. Ignoring Config Precision
// ❌ 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. Not Handling Velocity
// ❌ Abrupt stop when interrupting animation
api.start({ x: 0 })
// ✅ Preserve momentum
api.start({
x: 0,
velocity: springs.x.getVelocity()
})5. Mixing Config Patterns
// ❌ 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. Animating Non-Numerical Values
// ❌ 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)`Resources
Scripts
spring_generator.py- Generate React Spring boilerplate codephysics_calculator.py- Calculate optimal spring physics parameters
References
react_spring_api.md- Complete React Spring hooks and API referencepopmotion_api.md- Popmotion functions and reactive streamsphysics_guide.md- Spring physics deep dive with tuning guide
Assets
starter_spring/- React + Vite template with React Spring examplesexamples/- Real-world patterns (gestures, scroll, 3D integration)
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.
React Spring Physics - Assets
This directory contains starter templates and example documentation for React Spring animations.
Contents
Starter Template (Recommended)
For a complete React + Vite starter template with React Spring examples, use the official template:
# Create new project with Vite
npm create vite@latest my-spring-app -- --template react
# Navigate and install
cd my-spring-app
npm install
# Add React Spring
npm install @react-spring/web
# Optional: Add gesture library
npm install @use-gesture/reactOfficial Examples
The React Spring team maintains excellent examples at:
- Documentation: https://react-spring.dev
- Examples: https://react-spring.dev/examples
- CodeSandbox: https://codesandbox.io/examples/package/@react-spring/web
Recommended Examples by Category
Basic Animations:
- Spring basics: https://codesandbox.io/s/react-spring-spring-vqqd5
- Trails: https://codesandbox.io/s/react-spring-trail-q0zq5
- Transitions: https://codesandbox.io/s/react-spring-transition-njgm6
Gesture Integration:
- Draggable cards: https://codesandbox.io/s/react-spring-draggable-list-xhqod
- Viewpager: https://codesandbox.io/s/react-spring-viewpager-8tsle
- Gesture examples: https://use-gesture.netlify.app/docs/examples/
Scroll Animations:
- Scroll progress: https://codesandbox.io/s/react-spring-scroll-progress-8nqwt
- Parallax: https://codesandbox.io/s/react-spring-parallax-sticky-xhdn7
- useScroll hook: https://react-spring.dev/docs/components/use-scroll
3D Integration (React Three Fiber):
- Spring animations in 3D: https://codesandbox.io/s/react-spring-3d-ijdj2
- Interactive 3D: https://docs.pmnd.rs/react-three-fiber/tutorials/v8-migration-guide#spring
Advanced Patterns:
- Chained animations: https://codesandbox.io/s/react-spring-chain-dxqgq
- Auto-height accordion: https://codesandbox.io/s/react-spring-auto-height-accordion-r4qku
- Masonry grid: https://codesandbox.io/s/react-spring-masonry-5bw7y
Quick Start Template
Minimal React Spring setup:
package.json
{
"name": "react-spring-starter",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"@react-spring/web": "^9.7.3",
"@use-gesture/react": "^10.3.0"
},
"devDependencies": {
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@vitejs/plugin-react": "^4.2.1",
"vite": "^5.0.8"
}
}src/App.jsx
import { useSpring, animated, config } from '@react-spring/web'
import './App.css'
function App() {
const [springs, api] = useSpring(() => ({
from: { y: -50, opacity: 0 }
}), [])
const handleClick = () => {
api.start({
from: { y: -50, opacity: 0 },
to: { y: 0, opacity: 1 },
config: config.wobbly
})
}
return (
<div className="app">
<animated.h1 style={springs}>
React Spring Physics
</animated.h1>
<button onClick={handleClick}>
Animate
</button>
<div className="examples">
<ExampleClick />
<ExampleTrail />
</div>
</div>
)
}
function ExampleClick() {
const [springs, api] = useSpring(() => ({
scale: 1,
config: { tension: 300, friction: 10 }
}), [])
return (
<animated.div
className="box"
onClick={() => api.start({ scale: 1.2 })}
style={{
transform: springs.scale.to(s => `scale(${s})`)
}}
>
Click Me
</animated.div>
)
}
function ExampleTrail() {
const items = ['React', 'Spring', 'Physics']
const trails = useTrail(items.length, {
from: { opacity: 0, x: -20 },
to: { opacity: 1, x: 0 },
config: config.gentle
})
return (
<div className="trail">
{trails.map((style, i) => (
<animated.div key={i} style={style} className="trail-item">
{items[i]}
</animated.div>
))}
</div>
)
}
export default Appsrc/App.css
.app {
text-align: center;
padding: 2rem;
}
.examples {
display: flex;
gap: 2rem;
justify-content: center;
margin-top: 2rem;
}
.box {
width: 100px;
height: 100px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
}
.trail {
display: flex;
gap: 1rem;
}
.trail-item {
padding: 1rem 2rem;
background: #f0f0f0;
border-radius: 8px;
font-weight: 500;
}Common Patterns
Pattern 1: Scroll-Triggered Animation
import { useInView, animated } from '@react-spring/web'
function ScrollReveal({ children }) {
const [ref, springs] = useInView(
() => ({
from: { opacity: 0, y: 50 },
to: { opacity: 1, y: 0 }
}),
{ rootMargin: '-20% 0%' }
)
return (
<animated.div ref={ref} style={springs}>
{children}
</animated.div>
)
}Pattern 2: List Transitions
import { useTransition, animated } from '@react-spring/web'
function AnimatedList({ items }) {
const transitions = useTransition(items, {
keys: item => item.id,
from: { opacity: 0, transform: 'translateX(-20px)' },
enter: { opacity: 1, transform: 'translateX(0px)' },
leave: { opacity: 0, transform: 'translateX(20px)' }
})
return transitions((style, item) => (
<animated.div style={style}>
{item.text}
</animated.div>
))
}Pattern 3: Gesture-Driven Drag
import { useSpring, animated } from '@react-spring/web'
import { useDrag } from '@use-gesture/react'
function DraggableCard() {
const [{ x, y }, api] = useSpring(() => ({ x: 0, y: 0 }))
const bind = useDrag(({ down, movement: [mx, my] }) => {
api.start({
x: down ? mx : 0,
y: down ? my : 0,
immediate: down
})
})
return (
<animated.div
{...bind()}
style={{ x, y, touchAction: 'none' }}
/>
)
}TypeScript Support
For TypeScript projects, install types:
npm install --save-dev @types/react @types/react-domReact Spring is fully typed - no additional @types package needed.
Testing
For testing with React Spring, disable animations:
import { Globals } from '@react-spring/web'
beforeAll(() => {
Globals.assign({ skipAnimation: true })
})
afterAll(() => {
Globals.assign({ skipAnimation: false })
})Additional Resources
- React Spring Docs: https://react-spring.dev
- API Reference: https://react-spring.dev/docs
- Discord Community: https://discord.com/invite/poimandres
- GitHub: https://github.com/pmndrs/react-spring
- Use Gesture: https://use-gesture.netlify.app (for drag/gesture support)
- Leva: https://github.com/pmndrs/leva (for live config tuning)
Performance Tips
1. Use function config for imperative control 2. Set appropriate precision to reduce updates 3. Use useSprings for batching similar animations 4. Avoid animating layout - prefer transforms and opacity 5. Enable skipAnimation for tests and accessibility 6. Monitor with React DevTools Profiler
---
For more examples and patterns, refer to the skill's references:
react_spring_api.md- Complete API documentationpopmotion_api.md- Low-level physics utilitiesphysics_guide.md- Spring tuning deep dive
Spring Physics Deep Dive
Understanding and tuning spring animations for natural, physically accurate motion.
Table of Contents
- Physics Fundamentals
- Spring Parameters
- Tuning Guide
- Common Configurations
- Velocity and Momentum
- Advanced Techniques
---
Physics Fundamentals
What is a Spring Animation?
Unlike duration-based animations (e.g., "move from A to B in 300ms"), spring animations simulate physical motion based on forces:
Hooke's Law:
F = -k * x
Where:
F = Force applied by spring
k = Spring constant (stiffness)
x = Displacement from rest positionDamping Force:
F_damping = -c * v
Where:
c = Damping coefficient (friction)
v = VelocityResult: Natural, bouncy motion that automatically calculates duration based on physics.
---
Spring Parameters
Mass
What it does: Determines object's weight/inertia.
Effect:
- Higher mass (e.g., 5): Heavier, slower to accelerate/decelerate
- Lower mass (e.g., 0.5): Lighter, more responsive
When to increase:
- Large UI elements (modals, panels)
- Dramatic, weighty animations
- Slow, deliberate movements
When to decrease:
- Small UI elements (tooltips, badges)
- Quick, snappy interactions
- Lightweight, responsive feel
Example:
// Heavy modal
{ mass: 5, tension: 170, friction: 26 }
// Lightweight tooltip
{ mass: 0.5, tension: 170, friction: 26 }---
Tension (Stiffness)
What it does: Spring's resistance to stretching (spring strength).
Effect:
- Higher tension (e.g., 300): Faster, stiffer spring
- Lower tension (e.g., 100): Slower, softer spring
When to increase:
- Quick, responsive interactions
- Snappy button feedback
- Alert animations
When to decrease:
- Gentle, smooth transitions
- Organic, flowing motion
- Relaxed page transitions
Example:
// Snappy button press
{ tension: 300, friction: 20 }
// Gentle fade-in
{ tension: 120, friction: 14 }---
Friction (Damping)
What it does: Opposing force that slows motion.
Effect:
- Higher friction (e.g., 40): Less overshoot, faster settling
- Lower friction (e.g**, 10): More bounce, oscillation
When to increase:
- Minimal overshoot desired
- Professional, controlled feel
- Quick settle time
When to decrease:
- Playful, bouncy animations
- Noticeable spring effect
- Dramatic overshoot
Example:
// Minimal bounce (controlled)
{ tension: 170, friction: 40 }
// Bouncy, playful
{ tension: 180, friction: 12 }---
Tuning Guide
Step-by-Step Tuning Process
1. Start with a preset:
import { config } from '@react-spring/web'
// Try these first:
config.default // Balanced
config.gentle // Smooth, slow
config.wobbly // Bouncy
config.stiff // Fast, minimal bounce2. Adjust tension for speed:
// Too slow? Increase tension
{ tension: 250, friction: 26 }
// Too fast? Decrease tension
{ tension: 120, friction: 26 }3. Adjust friction for bounce:
// Too bouncy? Increase friction
{ tension: 170, friction: 35 }
// Not bouncy enough? Decrease friction
{ tension: 170, friction: 15 }4. Adjust mass for weight:
// Feels too light? Increase mass
{ mass: 3, tension: 170, friction: 26 }
// Too heavy/sluggish? Decrease mass
{ mass: 0.7, tension: 170, friction: 26 }---
Visual Tuning Tool
React Spring provides an interactive tuning tool:
import { useSpring, animated } from '@react-spring/web'
import { useControls } from 'leva'
function Tuner() {
const config = useControls({
mass: { value: 1, min: 0.1, max: 10 },
tension: { value: 170, min: 1, max: 500 },
friction: { value: 26, min: 1, max: 100 }
})
const springs = useSpring({
from: { x: 0 },
to: { x: 100 },
config
})
return <animated.div style={springs} />
}Install leva: npm install leva
---
Common Configurations
By Use Case
Button Interactions (Quick, Responsive):
{ mass: 1, tension: 300, friction: 20 }
// Fast response, minimal bounceModal Animations (Dramatic, Smooth):
{ mass: 2, tension: 150, friction: 30 }
// Weighty, controlled entrancePage Transitions (Smooth, Professional):
{ mass: 1, tension: 120, friction: 14 }
// Gentle, flowing motionNotifications/Toasts (Bouncy, Attention-Grabbing):
{ mass: 1, tension: 180, friction: 12 }
// Noticeable bounce, playfulDrag-and-Drop (Natural Physics):
{ mass: 1, tension: 200, friction: 25 }
// Balanced, realistic feelLoading Indicators (Continuous, Smooth):
{ mass: 1, tension: 100, friction: 30 }
// Slow, controlled oscillation---
By Animation Feel
Snappy:
{ mass: 0.8, tension: 300, friction: 20 }Bouncy:
{ mass: 1, tension: 180, friction: 12 }Smooth/Gentle:
{ mass: 1, tension: 120, friction: 14 }Stiff/Controlled:
{ mass: 1, tension: 210, friction: 20 }Slow/Lazy:
{ mass: 1, tension: 280, friction: 60 }Heavy/Dramatic:
{ mass: 5, tension: 170, friction: 26 }---
Velocity and Momentum
Initial Velocity
Set starting velocity for momentum-based animations:
const [springs, api] = useSpring(() => ({
x: 0,
config: { tension: 200, friction: 20 }
}), [])
api.start({
x: 100,
velocity: 500 // px/second
})Preserving Velocity (Interruptions)
Maintain momentum when interrupting an animation:
const handleInterrupt = () => {
api.start({
x: newTarget,
velocity: springs.x.getVelocity(), // Current velocity
config: { tension: 300, friction: 25 }
})
}Why this matters:
- Natural feel when user changes direction mid-animation
- Smooth transitions between gesture states
- Realistic physics simulation
---
Calculating Velocity from Gestures
import { useDrag } from '@use-gesture/react'
import { useSpring, animated } from '@react-spring/web'
function DraggableElement() {
const [{ x }, api] = useSpring(() => ({ x: 0 }))
const bind = useDrag(({ down, movement: [mx], velocity: [vx] }) => {
api.start({
x: down ? mx : 0,
velocity: vx * 1000, // Convert to px/second
immediate: down,
config: { tension: 200, friction: 30 }
})
})
return <animated.div {...bind()} style={{ x }} />
}---
Advanced Techniques
Conditional Spring Config
Different physics for different properties:
const springs = useSpring({
x: 100,
scale: 1.5,
opacity: 1,
config: {
x: { tension: 300, friction: 20 }, // Fast horizontal
scale: { mass: 4, friction: 10 }, // Heavy scaling
opacity: { tension: 120, friction: 14 } // Gentle fade
}
})Config as Function
Dynamic configuration based on property:
const springs = useSpring({
x: 100,
y: 200,
scale: 1.5,
config: (key) => {
if (key === 'scale') {
return { mass: 4, friction: 10 }
}
if (key === 'x') {
return { tension: 300, friction: 20 }
}
return config.default
}
})Spring with Clamp
Prevent overshoot entirely:
const springs = useSpring({
x: 100,
config: {
tension: 300,
friction: 20,
clamp: true // No overshoot
}
})---
Duration Override
Force a specific duration (not physically accurate):
const springs = useSpring({
x: 100,
config: {
duration: 500, // Override physics
easing: t => t // Linear easing
}
})Note: When using duration, the animation is no longer physics-based. It becomes a traditional tween.
---
Precision Tuning
Control when animation "completes":
const springs = useSpring({
x: 100,
config: {
tension: 170,
friction: 26,
precision: 0.01 // Stop when within 0.01 of target (default: 0.0001)
}
})Higher precision (0.1):
- Fewer updates
- Earlier completion
- Better performance
- Slight imprecision
Lower precision (0.0001):
- More updates
- Exact final value
- Slower performance
- Pixel-perfect
---
Troubleshooting
Animation Too Fast
Solutions: 1. Decrease tension (e.g., 170 → 120) 2. Increase mass (e.g., 1 → 2) 3. Increase friction (e.g., 26 → 40)
Animation Too Slow
Solutions: 1. Increase tension (e.g., 170 → 250) 2. Decrease mass (e.g., 1 → 0.7) 3. Decrease friction (e.g., 26 → 15)
Too Much Bounce/Overshoot
Solutions: 1. Increase friction (e.g., 26 → 40) 2. Enable clamp: true 3. Use config.stiff preset
Not Enough Bounce
Solutions: 1. Decrease friction (e.g., 26 → 12) 2. Use config.wobbly preset 3. Increase tension slightly (e.g., 170 → 180)
Animation Never Completes
Solutions: 1. Increase precision (e.g., 0.0001 → 0.01) 2. Check for conflicting animations 3. Ensure restSpeed is not set too low
---
Mathematical Relationships
Critical Damping
The point where spring returns to rest without oscillating:
c_critical = 2 * sqrt(k * m)
Where:
c = friction
k = tension
m = massDamping Ratio:
ζ = c / (2 * sqrt(k * m))
ζ > 1: Over-damped (no overshoot, slow)
ζ = 1: Critically damped (no overshoot, fast)
ζ < 1: Under-damped (overshoot, oscillation)Example:
// Calculate critically damped friction
const mass = 1
const tension = 170
const criticalFriction = 2 * Math.sqrt(tension * mass)
// criticalFriction ≈ 26 (default React Spring config!)
// Under-damped (bouncy)
{ mass: 1, tension: 170, friction: 12 } // ζ ≈ 0.46
// Critically damped
{ mass: 1, tension: 170, friction: 26 } // ζ = 1
// Over-damped (slow)
{ mass: 1, tension: 170, friction: 40 } // ζ ≈ 1.53---
Comparison: Duration vs Physics
Duration-Based (Traditional)
// CSS transition
transition: all 300ms ease-in-out;
// React Spring duration override
{ duration: 300, easing: easeInOut }Pros:
- Predictable timing
- Easier to sync with other events
- Consistent across devices
Cons:
- Feels artificial when interrupted
- Loses momentum on direction change
- Not physically accurate
---
Physics-Based (Spring)
{ mass: 1, tension: 170, friction: 26 }Pros:
- Natural, organic feel
- Interruptible without jarring
- Preserves momentum
- Realistic physics
Cons:
- Variable duration (depends on distance, velocity)
- Harder to sync precisely
- Requires tuning for desired feel
---
Best Practices
1. Start with presets - Use config.default, config.gentle, etc. 2. Tune visually - Use Leva or similar tool to adjust in real-time 3. Preserve velocity - Use getVelocity() for interruptions 4. Set appropriate precision - Higher for better performance 5. Test on target devices - Physics may feel different on slow devices 6. Use duration sparingly - Only when exact timing is critical 7. Document custom configs - Add comments explaining physics choices 8. Consistency matters - Use similar configs for similar interactions
---
Resources
- Spring Physics Calculator - Interactive tuning
- Leva - Real-time config editor
- Hooke's Law - Physics fundamentals
- Damped Harmonic Oscillator - Spring theory
Popmotion API Reference
Low-level animation library providing composable animation functions. Useful for advanced physics simulations and custom animation logic.
Table of Contents
---
Core Animations
spring
Physics-based spring animation.
Signature:
spring(config: SpringConfig): AnimationConfig Properties:
interface SpringConfig {
from?: number | object | array
to?: number | object | array
stiffness?: number // Spring strength (default: 100)
damping?: number // Opposing force (default: 10)
mass?: number // Object mass (default: 1)
velocity?: number // Initial velocity (default: 0)
restSpeed?: number // Stop threshold (default: 0.001)
restDelta?: number // Position threshold (default: 0.01)
}Examples:
import { spring } from 'popmotion'
// Basic spring
spring({
from: 0,
to: 100,
stiffness: 200,
damping: 20
}).start(v => console.log(v))
// With velocity
spring({
from: ballXY.get(),
velocity: ballXY.getVelocity(),
stiffness: 300,
damping: 10
}).start(ballXY)
// Object values
spring({
from: { x: 0, y: 0 },
to: { x: 100, y: 200 },
stiffness: { x: 200, y: 1000 },
damping: { x: 10, y: 50 }
}).start(({ x, y }) => console.log(x, y))Value Types:
- Numbers:
spring({ from: 0, to: 100 }) - Units:
spring({ from: '0px', to: '100px' }) - Colors:
spring({ from: '#fff', to: '#000' }) - Objects:
spring({ from: { x: 0 }, to: { x: 100 } }) - Arrays:
spring({ from: [0, 0], to: [100, 200] })
---
inertia
Momentum-based deceleration with spring-loaded boundaries.
Signature:
inertia(config: InertiaConfig): AnimationConfig Properties:
interface InertiaConfig {
from?: number
velocity?: number // Initial velocity (required)
power?: number // Deceleration strength (default: 0.8)
timeConstant?: number // Deceleration duration (default: 350)
restDelta?: number // Stop threshold (default: 0.5)
min?: number // Minimum boundary
max?: number // Maximum boundary
bounceStiffness?: number // Boundary spring strength (default: 500)
bounceDamping?: number // Boundary spring damping (default: 10)
modifyTarget?: (v: number) => number // Snap function
}Examples:
import { inertia } from 'popmotion'
// Basic momentum scroll
inertia({
from: 50,
velocity: 500
}).start(v => console.log(v))
// With boundaries
inertia({
from: 50,
velocity: 500,
min: 0,
max: 1000,
bounceStiffness: 1000,
bounceDamping: 300
}).start(v => console.log(v))
// Snap to grid
inertia({
from: 50,
velocity: 200,
modifyTarget: v => Math.round(v / 100) * 100
}).start(v => console.log(v))Use Cases:
- Momentum scrolling
- Swipe-to-dismiss
- Physics-based drag release
- Snap-to-grid animations
---
keyframes
Animate through a sequence of values.
Signature:
keyframes(config: KeyframesConfig): AnimationConfig Properties:
interface KeyframesConfig {
values: any[] // Keyframe values
times?: number[] // Progress points (0-1)
duration?: number // Total duration (default: 300)
easings?: Easing[] // Per-segment easings
}Examples:
import { keyframes, linear, easeInOut } from 'popmotion'
// Basic keyframes
keyframes({
values: [0, 100, 200],
duration: 1000
}).start(v => console.log(v))
// With times and easings
keyframes({
values: [0, 100, 200],
times: [0, 0.2, 1],
duration: 1000,
easings: [linear, easeInOut]
}).start(v => console.log(v))
// Color animation
keyframes({
values: ['#fff', '#000', '#f00'],
duration: 2000
}).start(color => console.log(color))---
decay
Exponential deceleration (no terminal velocity).
Signature:
decay(config: DecayConfig): AnimationConfig Properties:
interface DecayConfig {
from?: number
velocity?: number // Initial velocity (required)
power?: number // Deceleration strength (default: 0.8)
timeConstant?: number // Controls decay rate (default: 350)
restDelta?: number // Stop threshold (default: 0.5)
modifyTarget?: (v: number) => number
}Example:
import { decay } from 'popmotion'
decay({
from: 0,
velocity: 1000,
power: 0.8,
timeConstant: 400
}).start(v => console.log(v))---
physics
Integrated physics simulation (velocity, acceleration, friction, springs).
Signature:
physics(config: PhysicsConfig): AnimationConfig Properties:
interface PhysicsConfig {
from?: number
velocity?: number // Units per second (default: 0)
acceleration?: number // Increase velocity (default: 0)
friction?: number // 0-1 deceleration (default: 0)
springStrength?: number // Spring force (with `to`)
to?: number // Spring target (with `springStrength`)
restSpeed?: number // Stop threshold (default: 0.001)
}Examples:
import { physics } from 'popmotion'
// Accelerating object
physics({
from: 0,
velocity: 0,
acceleration: 100
}).start(v => console.log(v))
// Friction-based deceleration
physics({
from: 0,
velocity: 1000,
friction: 0.8
}).start(v => console.log(v))
// Spring simulation
physics({
from: 0,
velocity: 1000,
friction: 0.8,
to: 400,
springStrength: 500
}).start(v => console.log(v))Playback Methods:
const animation = physics({ from: 0, velocity: 100 }).start(v => {})
animation.setVelocity(500)
animation.setAcceleration(200)
animation.setFriction(0.9)
animation.setSpringStrength(600)
animation.setSpringTarget(500)
animation.stop()---
Animation Control
All animations return playback controls via .start().
Basic Start:
animation.start(v => {
// Update callback
})With Complete Callback:
animation.start({
update: v => { /* on update */ },
complete: () => { /* on complete */ }
})Common Methods:
const controls = animation.start(v => {})
controls.stop() // Stop animationChaining Methods:
// Filter values
spring({ from: 0, to: 100 })
.filter(v => v > 50)
.start(v => console.log(v)) // Only outputs values > 50
// Transform output
spring({ from: 0, to: 100 })
.pipe(Math.round, v => v * 2)
.start(v => console.log(v)) // Rounded and doubled
// Conditional completion
spring({ from: 0, to: 100 })
.while(v => v < 75)
.start(v => console.log(v)) // Stops when v >= 75---
Easing Functions
Importing:
import {
linear,
easeIn, easeOut, easeInOut,
circIn, circOut, circInOut,
backIn, backOut, backInOut,
anticipate,
cubicBezier
} from 'popmotion'Built-in Easings:
linear- Constant speedeaseIn, easeOut, easeInOut- Quadratic easingcircIn, circOut, circInOut- Circular easingbackIn, backOut, backInOut- Back easing (overshoot)anticipate- Pull back then overshoot
Custom Cubic Bezier:
import { cubicBezier } from 'popmotion'
const customEase = cubicBezier(0.17, 0.67, 0.83, 0.67)Creating Custom Easings:
import {
createExpoIn,
createBackIn,
createAnticipate,
mirrorEasing,
reverseEasing
} from 'popmotion'
// Exponential easing
const expoIn = createExpoIn(4)
const expoOut = mirrorEasing(expoIn)
const expoInOut = reverseEasing(expoIn)
// Back easing with custom overshoot
const backIn = createBackIn(4)
// Anticipate with custom power
const anticipate = createAnticipate(4)Usage with Keyframes:
import { keyframes, easeInOut } from 'popmotion'
keyframes({
values: [0, 100],
duration: 1000,
easings: [easeInOut]
}).start(v => console.log(v))---
Utilities
animate
High-level animation API (similar to GSAP/CSS animations).
import { animate, spring, linear } from 'popmotion'
// Duration-based
animate({
from: 0,
to: 100,
duration: 1000,
ease: linear
}).start(v => console.log(v))
// Spring-based
animate({
from: 0,
to: 100,
type: 'spring',
stiffness: 1000,
damping: 50
}).start(v => console.log(v))Multi-value Animations
Objects:
spring({
from: { x: 0, y: 0 },
to: { x: 100, y: 200 }
}).start(({ x, y }) => {
console.log(`x: ${x}, y: ${y}`)
})Arrays:
spring({
from: [0, 0],
to: [100, 200]
}).start(([x, y]) => {
console.log(`x: ${x}, y: ${y}`)
})Complex Strings:
spring({
from: '0px 0px 0px inset rgba(0, 0, 0, 0.2)',
to: '3px 3px 10px inset rgba(0, 0, 0, 0.5)'
}).start(shadow => {
element.style.boxShadow = shadow
})---
Integration with React Spring
Popmotion is typically used for advanced use cases where React Spring's declarative API isn't sufficient.
Example: Custom Physics:
import { physics } from 'popmotion'
import { useState, useEffect } from 'react'
function CustomPhysics() {
const [x, setX] = useState(0)
const handleDragEnd = (velocity) => {
physics({
from: x,
velocity: velocity,
friction: 0.8,
to: 0,
springStrength: 500
}).start(setX)
}
return <div style={{ transform: `translateX(${x}px)` }} />
}Example: Snap-to-Grid Inertia:
import { inertia } from 'popmotion'
import { useState } from 'react'
function SnapGrid() {
const [x, setX] = useState(0)
const handleDragEnd = (velocity) => {
inertia({
from: x,
velocity: velocity,
modifyTarget: v => Math.round(v / 100) * 100,
bounceStiffness: 1000,
bounceDamping: 300
}).start(setX)
}
return <div style={{ transform: `translateX(${x}px)` }} />
}---
Performance Notes
1. Composable - Chain .filter(), .pipe(), .while() for custom behavior 2. Low-level - No React overhead, direct DOM manipulation 3. Tree-shakeable - Import only what you need 4. Zero dependencies - Tiny bundle size (~5KB) 5. 60fps - Optimized for frame-based updates
---
Common Patterns
Drag with Inertia
import { inertia } from 'popmotion'
let velocity = 0
// Track velocity during drag
const handleDrag = (dx, dt) => {
velocity = dx / dt
}
// Apply inertia on release
const handleDragEnd = () => {
inertia({
from: currentX,
velocity: velocity,
power: 0.8,
timeConstant: 400
}).start(setX)
}Spring Taper (Follow Pointer)
import { physics } from 'popmotion'
const springTo = physics({
velocity: ballXY.getVelocity(),
friction: 0.6,
springStrength: 400,
to: ballXY.get(),
restSpeed: false
}).start(ballXY)
pointer(ballXY.get())
.start(v => springTo.setSpringTarget(v))Boundary Constraints
import { spring } from 'popmotion'
const handleRelease = () => {
const x = handleX.get()
if (x < 0 || x > 250) {
spring({
from: x,
to: x < 0 ? 0 : 250,
velocity: handleX.getVelocity(),
stiffness: 900,
damping: 30
}).start(handleX)
} else {
handleX.stop()
}
}React Spring API Reference
Complete API documentation for React Spring hooks, components, and utilities.
Table of Contents
- Core Hooks
- useSpring
- useSprings
- useTrail
- useTransition
- useSpringValue
- Utility Hooks
- useScroll
- useInView
- useSpringRef
- useIsomorphicLayoutEffect
- Components
- animated
- Configuration
- Config Presets
- Config Properties
- API Methods
- SpringRef API
- SpringValue Methods
- Events
- Globals
---
Core Hooks
useSpring
Create a single spring animation.
TypeScript Signature (Object Config):
function useSpring(config: SpringConfig): SpringValuesTypeScript Signature (Function Config):
function useSpring(
configFn: () => SpringConfig,
deps?: any[]
): [SpringValues, SpringRef]Parameters:
configorconfigFn- Animation configurationdeps- Dependency array for re-evaluation (function config only)
Returns:
- Object config:
SpringValuesfor rendering - Function config:
[SpringValues, SpringRef]tuple
Example (Object Config):
const springs = useSpring({
from: { opacity: 0 },
to: { opacity: 1 },
config: { tension: 170, friction: 26 }
})Example (Function Config):
const [springs, api] = useSpring(() => ({
from: { opacity: 0 },
config: { tension: 170, friction: 26 }
}), [])
// Trigger animation imperatively
api.start({ to: { opacity: 1 } })---
useSprings
Create multiple spring animations with a unified API.
TypeScript Signature (Object Config):
function useSprings(count: number, config: SpringConfig): SpringValues[]TypeScript Signature (Function Config):
function useSprings(
count: number,
configFn: (index: number) => SpringConfig,
deps?: any[]
): [SpringValues[], SpringRef]Parameters:
count- Number of springs to createconfigorconfigFn- Configuration (function receives index)deps- Dependency array for re-evaluation
Returns:
- Object config:
SpringValues[]array - Function config:
[SpringValues[], SpringRef]tuple
Example:
const springs = useSprings(
items.length,
items.map((item, i) => ({
from: { opacity: 0, x: -20 },
to: { opacity: 1, x: 0 },
delay: i * 100
}))
)---
useTrail
Create a trailing animation where each spring follows the previous.
TypeScript Signature (Object Config):
function useTrail(count: number, config: SpringConfig): SpringValues[]TypeScript Signature (Function Config):
function useTrail(
count: number,
configFn: () => SpringConfig,
deps?: any[]
): [SpringValues[], SpringRef]Example:
const trails = useTrail(5, {
from: { opacity: 0, x: -20 },
to: { opacity: 1, x: 0 },
config: config.gentle
})---
useTransition
Animate a dataset with enter/leave transitions.
TypeScript Signature (Object Config):
function useTransition<Item>(
data: Item[],
config: TransitionConfig<Item>
): TransitionFnTypeScript Signature (Function Config):
function useTransition<Item>(
data: Item[],
configFn: () => TransitionConfig<Item>,
deps?: any[]
): [TransitionFn, SpringRef]TransitionConfig Properties:
from- Initial styles for entering itemsenter- Target styles for entered itemsleave- Exit styles for leaving itemsupdate- Styles for items that update (optional)keys- Function or key to identify items
Example:
const transitions = useTransition(items, {
from: { opacity: 0, height: 0 },
enter: { opacity: 1, height: 80 },
leave: { opacity: 0, height: 0 },
keys: item => item.id
})
return transitions((style, item) => (
<animated.div style={style}>{item.text}</animated.div>
))---
useSpringValue
Create a single animated value.
TypeScript Signature:
function useSpringValue<T>(
initial: T,
config?: SpringConfig
): SpringValue<T>Example:
const opacity = useSpringValue(0, {
config: { mass: 2, friction: 5, tension: 80 }
})
// Update value
opacity.start(1)---
Utility Hooks
useScroll
Track scroll position with spring physics.
TypeScript Signature:
function useScroll(config?: ScrollConfig): {
scrollX: SpringValue<number>
scrollY: SpringValue<number>
scrollXProgress: SpringValue<number>
scrollYProgress: SpringValue<number>
}ScrollConfig Properties:
container- Scroll container ref (default: window)config- Spring configuration
Example:
const { scrollYProgress } = useScroll()
return (
<animated.div style={{ opacity: scrollYProgress }}>
Fades in as you scroll
</animated.div>
)---
useInView
Trigger animation when element enters viewport.
TypeScript Signature:
function useInView<T extends HTMLElement>(
configFn: () => SpringConfig,
options?: IntersectionObserverInit
): [RefCallback<T>, SpringValues]Options (IntersectionObserverInit):
root- Viewport element (default: browser viewport)rootMargin- Margin around root (e.g., '-40% 0%')threshold- Visibility threshold (0-1)
Example:
const [ref, springs] = useInView(
() => ({
from: { opacity: 0, y: 100 },
to: { opacity: 1, y: 0 }
}),
{ rootMargin: '-20% 0%' }
)
return <animated.div ref={ref} style={springs}>Content</animated.div>---
useSpringRef
Create a ref for controlling springs imperatively.
TypeScript Signature:
function useSpringRef(): SpringRefExample:
const api = useSpringRef()
const springs = useSpring({
ref: api,
from: { opacity: 0 },
to: { opacity: 1 }
})
// Control via ref
api.start({ opacity: 0.5 })---
useIsomorphicLayoutEffect
Cross-platform useLayoutEffect (server-safe).
TypeScript Signature:
function useIsomorphicLayoutEffect(
effect: EffectCallback,
deps?: DependencyList
): voidUsage: Use like useLayoutEffect but works on server-side rendering.
---
Components
animated
Higher-Order Component to make elements animatable.
Built-in Animated Components:
import { animated } from '@react-spring/web'
animated.div
animated.span
animated.p
animated.svg
animated.path
animated.g
// ... all HTML elementsCustom Component Animation:
import { animated } from '@react-spring/web'
import { CustomComponent } from './CustomComponent'
const AnimatedCustom = animated(CustomComponent)
// Component must forward style prop to native element
function CustomComponent({ style, ...props }) {
return <div style={style} {...props} />
}Three.js Integration:
import { animated } from '@react-spring/three'
import { MeshDistortMaterial } from '@react-three/drei'
const AnimatedMaterial = animated(MeshDistortMaterial)---
Configuration
Config Presets
Pre-defined spring configurations for common animation feels.
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 }Usage:
useSpring({
from: { x: 0 },
to: { x: 100 },
config: config.wobbly
})---
Config Properties
Fine-tune spring physics manually.
SpringConfig Interface:
interface SpringConfig {
mass?: number // Mass of object (default: 1)
tension?: number // Spring strength (default: 170)
friction?: number // Opposing force (default: 26)
clamp?: boolean // Prevent overshooting (default: false)
precision?: number // Stop threshold (default: 0.0001)
velocity?: number // Initial velocity (default: 0)
duration?: number // Override physics with fixed duration
easing?: EasingFunction // Easing function (requires duration)
bounce?: number // Bounce factor 0-1 (alternative to tension/friction)
}Property-Specific Config:
useSpring({
x: 100,
y: 200,
config: {
x: { tension: 300, friction: 20 }, // Fast horizontal
y: { tension: 100, friction: 30 } // Slow vertical
}
})Config as Function:
useSpring({
x: 100,
scale: 1.5,
config: (key) => {
if (key === 'scale') return { mass: 4, friction: 10 }
return config.default
}
})---
API Methods
SpringRef API
Imperative control interface returned by function-config hooks.
Methods:
api.start(config)
Start or update animation.
api.start({
from: { x: 0 },
to: { x: 100 },
config: { tension: 200 },
onRest: () => console.log('Done!')
})api.pause()
Pause all animations.
api.pause()api.resume()
Resume paused animations.
api.resume()api.stop()
Stop all animations immediately.
api.stop()api.set(values)
Instantly set values without animating.
api.set({ x: 100, opacity: 1 })---
SpringValue Methods
Methods available on individual SpringValue instances.
get() - Get current value:
const currentX = springs.x.get()getVelocity() - Get current velocity:
const velocity = springs.x.getVelocity()to() - Transform value:
<animated.div
style={{
transform: springs.x.to(x => `translateX(${x}px)`)
}}
/>start() - Animate this value:
springs.opacity.start(1)---
Events
Event callbacks for animation lifecycle.
Event Properties:
interface AnimationProps {
onStart?: (result: AnimationResult) => void
onChange?: (result: AnimationResult) => void
onRest?: (result: AnimationResult) => void
onPause?: () => void
onResume?: () => void
}Global Events:
useSpring({
x: 100,
onStart: () => console.log('Animation started'),
onRest: () => console.log('Animation completed')
})Key-Specific Events:
useSpring({
x: 100,
y: 200,
onStart: {
x: () => console.log('x started'),
y: () => console.log('y started')
}
})---
Globals
Global configuration for all animations.
Globals.assign(config):
import { Globals } from '@react-spring/web'
// Skip all animations (accessibility)
Globals.assign({ skipAnimation: true })
// Custom frame loop
Globals.assign({ frameLoop: 'always' }) // or 'demand'
// Custom performance now
Globals.assign({ now: () => performance.now() })Common Use Cases:
// Prefers reduced motion
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)')
Globals.assign({ skipAnimation: mediaQuery.matches })
}, [])
// Testing mode
if (process.env.NODE_ENV === 'test') {
Globals.assign({ skipAnimation: true })
}---
Advanced Patterns
Chaining Animations
const springs = useSpring({
from: { x: 0, background: '#ff6d6d' },
to: [
{ x: 100, background: '#fff59a' },
{ x: 0, background: '#88DFAB' }
],
loop: true
})Async to Function
const springs = useSpring({
from: { x: 0 },
to: async (next) => {
await next({ x: 100 })
await next({ x: 50 })
await next({ x: 0 })
}
})Conditional Animation
const [springs, api] = useSpring(() => ({
x: 0
}), [])
useEffect(() => {
if (condition) {
api.start({ x: 100 })
} else {
api.start({ x: 0 })
}
}, [condition])Interpolation
<animated.div
style={{
transform: springs.x.to({
range: [0, 0.5, 1],
output: ['translateX(0px)', 'translateX(50px)', 'translateX(100px)']
})
}}
/>---
Performance Tips
1. Use function config for imperative control - Avoids recreation on render 2. Set appropriate precision - Higher values reduce updates 3. Batch similar animations - Use useSprings for multiple similar items 4. Skip animations in tests - Use Globals.assign({ skipAnimation: true }) 5. Avoid animating layout - Prefer transforms and opacity 6. Use `immediate` for instant changes - api.start({ x: 100, immediate: true })
---
TypeScript Support
React Spring is fully typed. Key interfaces:
import type {
SpringValue,
SpringValues,
SpringRef,
SpringConfig,
AnimationResult
} from '@react-spring/web'Typing Custom Animations:
interface MySpringValues {
x: number
opacity: number
color: string
}
const springs = useSpring<MySpringValues>({
from: { x: 0, opacity: 0, color: '#fff' },
to: { x: 100, opacity: 1, color: '#000' }
})#!/usr/bin/env python3
"""
Spring Physics Calculator
Calculate optimal spring physics parameters for desired animation feel.
Usage:
./physics_calculator.py # Interactive mode
./physics_calculator.py --feel bouncy # Calculate preset
./physics_calculator.py --tension 200 --friction 20 # Custom params
./physics_calculator.py --critical-damping --tension 170 # Calculate critical
Physics Concepts:
- Mass: Weight/inertia of animated object
- Tension: Spring strength (higher = faster)
- Friction: Opposing force (higher = less bounce)
- Critical Damping: No overshoot, fastest settle time
"""
import sys
import argparse
import math
from textwrap import dedent
# Preset configurations
PRESETS = {
'default': {'mass': 1, 'tension': 170, 'friction': 26},
'gentle': {'mass': 1, 'tension': 120, 'friction': 14},
'wobbly': {'mass': 1, 'tension': 180, 'friction': 12},
'stiff': {'mass': 1, 'tension': 210, 'friction': 20},
'slow': {'mass': 1, 'tension': 280, 'friction': 60},
'molasses': {'mass': 1, 'tension': 280, 'friction': 120},
'snappy': {'mass': 0.8, 'tension': 300, 'friction': 20},
'bouncy': {'mass': 1, 'tension': 180, 'friction': 12},
'smooth': {'mass': 1, 'tension': 120, 'friction': 14},
'heavy': {'mass': 5, 'tension': 170, 'friction': 26}
}
def calculate_damping_ratio(mass, tension, friction):
"""Calculate damping ratio (ζ) from spring parameters."""
critical_friction = 2 * math.sqrt(tension * mass)
damping_ratio = friction / critical_friction
return damping_ratio
def calculate_critical_friction(mass, tension):
"""Calculate critical damping friction."""
return 2 * math.sqrt(tension * mass)
def classify_damping(damping_ratio):
"""Classify damping behavior."""
if damping_ratio < 1:
return "Under-damped (oscillates/bounces)"
elif damping_ratio == 1:
return "Critically damped (no overshoot, fastest)"
else:
return "Over-damped (slow, no overshoot)"
def estimate_settle_time(mass, tension, friction):
"""Estimate approximate settle time in milliseconds."""
damping_ratio = calculate_damping_ratio(mass, tension, friction)
natural_frequency = math.sqrt(tension / mass)
if damping_ratio >= 1:
# Over-damped or critically damped
settle_time = 4 / (damping_ratio * natural_frequency)
else:
# Under-damped
damped_frequency = natural_frequency * math.sqrt(1 - damping_ratio**2)
settle_time = 4 / (damping_ratio * natural_frequency)
return settle_time * 1000 # Convert to ms
def display_config(config, name=None):
"""Display spring configuration with analysis."""
mass = config['mass']
tension = config['tension']
friction = config['friction']
damping_ratio = calculate_damping_ratio(mass, tension, friction)
critical_friction = calculate_critical_friction(mass, tension)
classification = classify_damping(damping_ratio)
settle_time = estimate_settle_time(mass, tension, friction)
if name:
print(f"\n{'=' * 60}")
print(f"Spring Configuration: {name}")
print(f"{'=' * 60}")
else:
print(f"\n{'=' * 60}")
print(f"Spring Configuration Analysis")
print(f"{'=' * 60}")
print(f"\nParameters:")
print(f" mass: {mass}")
print(f" tension: {tension}")
print(f" friction: {friction}")
print(f"\nPhysics Analysis:")
print(f" Damping ratio (ζ): {damping_ratio:.3f}")
print(f" Critical friction: {critical_friction:.2f}")
print(f" Classification: {classification}")
print(f" Est. settle time: ~{settle_time:.0f}ms")
print(f"\nReact Spring Code:")
print(f" config: {{ mass: {mass}, tension: {tension}, friction: {friction} }}")
if abs(damping_ratio - 1.0) < 0.05:
print(f"\n💡 Near critically damped - very efficient!")
elif damping_ratio < 0.5:
print(f"\n💡 Very bouncy - expect multiple oscillations")
elif damping_ratio > 1.5:
print(f"\n💡 Heavily damped - may feel sluggish")
def interactive_mode():
"""Run calculator in interactive mode."""
print("Spring Physics Calculator")
print("=" * 60)
print("\nChoose an option:")
print(" 1. Use preset configuration")
print(" 2. Enter custom parameters")
print(" 3. Calculate critical damping")
print("\nChoice (1-3): ", end='')
try:
choice = input().strip()
except (KeyboardInterrupt, EOFError):
print("\n\nCancelled.")
return
if choice == '1':
# Preset mode
print("\nAvailable presets:\n")
for i, (name, config) in enumerate(PRESETS.items(), 1):
print(f" {i:2}. {name:12} - mass: {config['mass']}, tension: {config['tension']}, friction: {config['friction']}")
print(f"\nSelect preset (1-{len(PRESETS)}): ", end='')
try:
preset_choice = int(input().strip()) - 1
preset_name = list(PRESETS.keys())[preset_choice]
config = PRESETS[preset_name]
display_config(config, preset_name)
except (ValueError, IndexError, KeyboardInterrupt, EOFError):
print("\nInvalid selection.")
return
elif choice == '2':
# Custom parameters
try:
print("\nEnter parameters:")
mass = float(input(" mass (default 1): ") or 1)
tension = float(input(" tension (default 170): ") or 170)
friction = float(input(" friction (default 26): ") or 26)
config = {'mass': mass, 'tension': tension, 'friction': friction}
display_config(config)
except (ValueError, KeyboardInterrupt, EOFError):
print("\nInvalid input.")
return
elif choice == '3':
# Critical damping calculator
try:
print("\nCalculate critical damping:")
mass = float(input(" mass (default 1): ") or 1)
tension = float(input(" tension (default 170): ") or 170)
critical_friction = calculate_critical_friction(mass, tension)
config = {'mass': mass, 'tension': tension, 'friction': critical_friction}
print(f"\n✅ Critical friction: {critical_friction:.2f}")
display_config(config, "Critically Damped")
except (ValueError, KeyboardInterrupt, EOFError):
print("\nInvalid input.")
return
else:
print("\nInvalid choice.")
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(
description='Calculate spring physics parameters',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=dedent('''
Examples:
./physics_calculator.py # Interactive mode
./physics_calculator.py --feel bouncy # Use preset
./physics_calculator.py --mass 2 --tension 200 --friction 30
./physics_calculator.py --critical --tension 170 --mass 1
./physics_calculator.py --list # List presets
''')
)
parser.add_argument(
'--feel',
choices=list(PRESETS.keys()),
help='Use preset configuration'
)
parser.add_argument(
'--mass', '-m',
type=float,
default=1,
help='Object mass (default: 1)'
)
parser.add_argument(
'--tension', '-t',
type=float,
default=170,
help='Spring tension/stiffness (default: 170)'
)
parser.add_argument(
'--friction', '-f',
type=float,
help='Spring friction/damping'
)
parser.add_argument(
'--critical', '-c',
action='store_true',
help='Calculate critical damping friction'
)
parser.add_argument(
'--list', '-l',
action='store_true',
help='List all preset configurations'
)
args = parser.parse_args()
if args.list:
print("\nAvailable preset configurations:\n")
for name, config in PRESETS.items():
damping_ratio = calculate_damping_ratio(config['mass'], config['tension'], config['friction'])
print(f" {name:12} - mass: {config['mass']:3}, tension: {config['tension']:3}, friction: {config['friction']:3} (ζ={damping_ratio:.2f})")
return 0
if args.feel:
config = PRESETS[args.feel]
display_config(config, args.feel)
return 0
if args.critical:
critical_friction = calculate_critical_friction(args.mass, args.tension)
config = {'mass': args.mass, 'tension': args.tension, 'friction': critical_friction}
display_config(config, "Critically Damped")
return 0
if args.friction is not None:
config = {'mass': args.mass, 'tension': args.tension, 'friction': args.friction}
display_config(config)
return 0
# No arguments - interactive mode
interactive_mode()
return 0
if __name__ == '__main__':
sys.exit(main())
#!/usr/bin/env python3
"""
React Spring Animation Generator
Generates React Spring boilerplate code for common animation patterns.
Usage:
./spring_generator.py # Interactive mode
./spring_generator.py --type click # Generate click animation
./spring_generator.py --type scroll # Generate scroll animation
./spring_generator.py --help # Show help
Animation Types:
click - Click-triggered spring animation
scroll - Scroll-based spring animation
trail - Multi-element trail animation
transition - List enter/exit transitions
inview - Viewport intersection animation
chain - Chained async animations
gesture - Gesture-driven animation (drag)
"""
import sys
import argparse
from textwrap import dedent
ANIMATION_TYPES = {
'click': {
'name': 'Click-Triggered Spring',
'code': '''import { useSpring, animated } from '@react-spring/web'
function ClickAnimation() {
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>
)
}
export default ClickAnimation'''
},
'scroll': {
'name': 'Scroll-Based Spring',
'code': '''import { useScroll, animated } from '@react-spring/web'
function ScrollAnimation() {
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>
)
}
export default ScrollAnimation'''
},
'trail': {
'name': 'Multi-Element Trail',
'code': '''import { useTrail, animated, config } from '@react-spring/web'
function TrailAnimation({ 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>
)
}
export default TrailAnimation'''
},
'transition': {
'name': 'List Enter/Exit Transitions',
'code': '''import { useTransition, animated, config } from '@react-spring/web'
function TransitionAnimation({ 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 (
<div>
{transitions((style, item) => (
<animated.div style={style}>
{item.text}
</animated.div>
))}
</div>
)
}
export default TransitionAnimation'''
},
'inview': {
'name': 'Viewport Intersection',
'code': '''import { useInView, animated } from '@react-spring/web'
function InViewAnimation() {
const [ref, springs] = useInView(
() => ({
from: { opacity: 0, y: 100 },
to: { opacity: 1, y: 0 }
}),
{ rootMargin: '-40% 0%' }
)
return (
<animated.div ref={ref} style={springs}>
Fades in when entering viewport
</animated.div>
)
}
export default InViewAnimation'''
},
'chain': {
'name': 'Chained Async Animations',
'code': '''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={{
width: 40,
height: 40,
borderRadius: 4,
...springs
}}
/>
)
}
export default ChainedAnimation'''
},
'gesture': {
'name': 'Gesture-Driven Animation',
'code': '''import { useSpring, animated } from '@react-spring/web'
import { useDrag } from '@use-gesture/react'
function GestureAnimation() {
const [{ x, y }, api] = useSpring(() => ({ x: 0, y: 0 }))
const bind = useDrag(({ down, movement: [mx, my], velocity: [vx, vy] }) => {
api.start({
x: down ? mx : 0,
y: down ? my : 0,
velocity: [vx * 1000, vy * 1000],
immediate: down,
config: { tension: 200, friction: 30 }
})
})
return (
<animated.div
{...bind()}
style={{
x,
y,
width: 100,
height: 100,
background: '#569AFF',
borderRadius: 8,
touchAction: 'none'
}}
/>
)
}
export default GestureAnimation'''
}
}
def generate_animation(anim_type, output_file=None):
"""Generate React Spring animation code."""
if anim_type not in ANIMATION_TYPES:
print(f"Error: Unknown animation type '{anim_type}'")
print(f"Available types: {', '.join(ANIMATION_TYPES.keys())}")
return False
animation = ANIMATION_TYPES[anim_type]
code = animation['code']
if output_file:
try:
with open(output_file, 'w') as f:
f.write(code)
print(f"✅ Generated {animation['name']} → {output_file}")
return True
except IOError as e:
print(f"❌ Error writing file: {e}")
return False
else:
print(f"\n// {animation['name']}\n")
print(code)
return True
def interactive_mode():
"""Run generator in interactive mode."""
print("React Spring Animation Generator")
print("=" * 50)
print("\nAvailable animation types:\n")
for i, (key, value) in enumerate(ANIMATION_TYPES.items(), 1):
print(f" {i}. {value['name']} ({key})")
print("\nSelect animation type (1-{}) or 'q' to quit: ".format(len(ANIMATION_TYPES)), end='')
try:
choice = input().strip()
except (KeyboardInterrupt, EOFError):
print("\n\nCancelled.")
return
if choice.lower() == 'q':
return
try:
index = int(choice) - 1
if 0 <= index < len(ANIMATION_TYPES):
anim_type = list(ANIMATION_TYPES.keys())[index]
else:
print("Invalid selection.")
return
except ValueError:
# Maybe they typed the key name
if choice in ANIMATION_TYPES:
anim_type = choice
else:
print("Invalid selection.")
return
print("\nOutput to file? (leave empty for stdout): ", end='')
try:
output_file = input().strip()
except (KeyboardInterrupt, EOFError):
print("\n\nCancelled.")
return
output_file = output_file if output_file else None
generate_animation(anim_type, output_file)
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(
description='Generate React Spring animation boilerplate',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=dedent('''
Examples:
./spring_generator.py # Interactive mode
./spring_generator.py --type click # Generate to stdout
./spring_generator.py --type scroll -o ScrollAnim.jsx
./spring_generator.py --list # List all types
''')
)
parser.add_argument(
'--type', '-t',
choices=list(ANIMATION_TYPES.keys()),
help='Animation type to generate'
)
parser.add_argument(
'--output', '-o',
help='Output file (default: stdout)'
)
parser.add_argument(
'--list', '-l',
action='store_true',
help='List all available animation types'
)
args = parser.parse_args()
if args.list:
print("Available animation types:\n")
for key, value in ANIMATION_TYPES.items():
print(f" {key:12} - {value['name']}")
return 0
if args.type:
success = generate_animation(args.type, args.output)
return 0 if success else 1
else:
# Interactive mode
interactive_mode()
return 0
if __name__ == '__main__':
sys.exit(main())
Related skills
FAQ
What does react-spring-physics do?
Physics-based animation library combining React Spring (spring dynamics, gesture integration, 60fps animations) and Popmotion (low-level composable animation utilities, reactive streams). Use when building fluid, natural
When should I use react-spring-physics?
Physics-based animation library combining React Spring (spring dynamics, gesture integration, 60fps animations) and Popmotion (low-level composable animation utilities, reactive streams). Use when building fluid, natural
What are common prerequisites?
--- name: react-spring-physics description: Physics-based animation library combining React Spring (spring dynamics, gesture integration, 60fps animations) and Popmotion (low-level composable animation utilities, reactiv
Is React Spring Physics safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.