react-spring-physics by freshtechbro/claudedesignskills
npx skills add https://github.com/freshtechbro/claudedesignskills --skill react-spring-physics基于物理的 React 应用动画,结合了 React Spring 的声明式弹簧动画与 Popmotion 的底层物理工具。
React Spring 提供感觉自然且可中断的弹簧物理动画。与基于持续时间的动画不同,弹簧根据物理属性(质量、张力、摩擦力)计算运动,从而产生有机、逼真的运动效果。Popmotion 通过可组合的关键帧、衰减和惯性动画函数对此进行补充。
何时使用此技能:
核心库:
@react-spring/web - 用于弹簧动画的 React 钩子@react-spring/three - Three.js 集成popmotion - 底层动画工具(可选,用于高级用例)弹簧使用物理模拟将值从当前状态动画到目标状态:
import { useSpring, animated } from '@react-spring/web'
function SpringExample() {
const springs = useSpring({
from: { opacity: 0, y: -40 },
to: { opacity: 1, y: 0 },
config: {
mass: 1, // 物体重量
tension: 170, // 弹簧强度
friction: 26 // 阻力
}
})
return <animated.div style={springs}>Hello</animated.div>
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
针对不同用例的两种初始化模式:
// 对象配置(更简单,属性变化时自动更新)
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 }
})
}
React Spring 提供内置的配置预设:
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
})
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>
)
}
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>
)
}
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>
))
}
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>
)
}
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>
}
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} />
}
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} />
}
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>
)
}
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)` }} />
}
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} />
}
// 仅在动画激活时重新渲染
const [springs, api] = useSpring(() => ({
from: { x: 0 },
config: { precision: 0.01 } // 值越高 = 更新越少
}), [])
// 对多个相似动画使用 useSprings
const springs = useSprings(
items.length,
items.map(item => ({
from: { opacity: 0 },
to: { opacity: 1 }
}))
)
import { Globals } from '@react-spring/web'
// 跳过所有动画(prefers-reduced-motion)
useEffect(() => {
Globals.assign({ skipAnimation: true })
return () => Globals.assign({ skipAnimation: false })
}, [])
// ❌ 错误:没有依赖项,每次渲染都会创建新的弹簧
const springs = useSpring(() => ({ x: 0 }))
// ✅ 正确:空数组防止重新创建
const [springs, api] = useSpring(() => ({ x: 0 }), [])
// ❌ 错误:直接修改
springs.x.set(100)
// ✅ 正确:使用 API 进行动画
api.start({ x: 100 })
// ❌ 默认精度过高(0.0001),导致不必要的渲染
const springs = useSpring({ x: 0 })
// ✅ 根据您的用例设置适当的精度
const springs = useSpring({
x: 0,
config: { precision: 0.01 } // 当与目标值相差在 0.01 以内时停止更新
})
// ❌ 中断动画时突然停止
api.start({ x: 0 })
// ✅ 保持动量
api.start({
x: 0,
velocity: springs.x.getVelocity()
})
// ❌ 错误:同时使用对象和函数配置
const springs = useSpring({
from: { x: 0 }
})
api.start({ x: 100 }) // api 未定义
// ✅ 正确:使用函数配置进行命令式控制
const [springs, api] = useSpring(() => ({
from: { x: 0 }
}), [])
// ❌ 错误:弹簧无法直接插值复杂字符串
const springs = useSpring({ transform: 'translateX(100px) rotate(45deg)' })
// ✅ 正确:动画化单个值
const springs = useSpring({ x: 100, rotation: 45 })
// 然后组合:transform: `translateX(${x}px) rotate(${rotation}deg)`
spring_generator.py - 生成 React Spring 样板代码physics_calculator.py - 计算最佳弹簧物理参数react_spring_api.md - 完整的 React Spring 钩子和 API 参考popmotion_api.md - Popmotion 函数和响应式流physics_guide.md - 弹簧物理深入探讨与调优指南starter_spring/ - 包含 React Spring 示例的 React + Vite 模板examples/ - 真实世界模式(手势、滚动、3D 集成)物理 vs 时间线:使用 React Spring 实现自然的、基于物理的运动,以响应用户输入。使用 GSAP 实现精确的、基于时间线的编排和复杂的多步骤序列。
每周安装量
75
代码仓库
GitHub 星标数
11
首次出现
2026年2月27日
安全审计
安装于
opencode74
claude-code73
cursor72
github-copilot72
codex72
amp72
Physics-based animation for React applications combining React Spring's declarative spring animations with Popmotion's low-level physics utilities.
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:
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)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>
}
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 }
})
}
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
})
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>
)
}
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>
)
}
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>
))
}
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>
)
}
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>
}
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} />
}
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} />
}
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>
)
}
import { spring, inertia } from 'popmotion'
import { useState } from 'react'
function PopmotionIntegration() {
const [x, setX] = useState(0)
const handleDragEnd = (velocity) => {
inertia({
from: x,
velocity: velocity,
power: 0.3,
timeConstant: 400,
modifyTarget: v => Math.round(v / 100) * 100 // Snap to grid
}).start(setX)
}
return <div style={{ transform: `translateX(${x}px)` }} />
}
import { useSpring, animated } from '@react-spring/web'
function ValidatedInput() {
const [error, setError] = useState(false)
const shakeAnimation = useSpring({
x: error ? [0, -10, 10, -10, 10, 0] : 0,
config: { tension: 300, friction: 10 },
onRest: () => setError(false)
})
return <animated.input style={shakeAnimation} />
}
// Only re-render when animation is active
const [springs, api] = useSpring(() => ({
from: { x: 0 },
config: { precision: 0.01 } // Higher value = less updates
}), [])
// Use useSprings for multiple similar animations
const springs = useSprings(
items.length,
items.map(item => ({
from: { opacity: 0 },
to: { opacity: 1 }
}))
)
import { Globals } from '@react-spring/web'
// Skip all animations (prefers-reduced-motion)
useEffect(() => {
Globals.assign({ skipAnimation: true })
return () => Globals.assign({ skipAnimation: false })
}, [])
// ❌ Wrong: No dependencies, creates new spring every render
const springs = useSpring(() => ({ x: 0 }))
// ✅ Correct: Empty array prevents recreation
const [springs, api] = useSpring(() => ({ x: 0 }), [])
// ❌ Wrong: Direct mutation
springs.x.set(100)
// ✅ Correct: Use API to animate
api.start({ x: 100 })
// ❌ 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
})
// ❌ Abrupt stop when interrupting animation
api.start({ x: 0 })
// ✅ Preserve momentum
api.start({
x: 0,
velocity: springs.x.getVelocity()
})
// ❌ 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 }
}), [])
// ❌ Wrong: Spring can't interpolate complex strings directly
const springs = useSpring({ transform: 'translateX(100px) rotate(45deg)' })
// ✅ Correct: Animate individual values
const springs = useSpring({ x: 100, rotation: 45 })
// Then combine: transform: `translateX(${x}px) rotate(${rotation}deg)`
spring_generator.py - Generate React Spring boilerplate codephysics_calculator.py - Calculate optimal spring physics parametersreact_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 guidestarter_spring/ - React + Vite template with React Spring examplesexamples/ - Real-world patterns (gestures, scroll, 3D integration)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.
Weekly Installs
75
Repository
GitHub Stars
11
First Seen
Feb 27, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode74
claude-code73
cursor72
github-copilot72
codex72
amp72
TanStack Table 中文指南:React 无头表格库,实现排序过滤分页
577 周安装