| name | spring-animation |
| description | Remotion spring physics for motion graphics video production. Bouncy entrances, elastic trails, orchestrated sequences, physics presets, and organic motion patterns that interpolate() alone cannot achieve. |
| metadata | {"tags":"spring, remotion, motion-graphics, animation, video, physics, bounce, elastic, trail, stagger"} |
When to use
Use this skill when creating Remotion video compositions that need spring physics -- natural, organic motion with bounce, overshoot, and elastic settling.
Use spring when you need:
- Bouncy/elastic entrances (overshoot + settle)
- Organic deceleration (not linear, not eased -- physically modeled)
- Staggered trails with spring physics per element
- Number counters that overshoot then settle
- Scale/rotate with natural weight and inertia
- Enter + exit animations with spring math (
in - out)
- Multi-property orchestration with different spring configs per property
Use Remotion native interpolate() when:
- Linear or eased motion with no bounce (fade, slide, wipe)
- Exact timing control (must end at precisely frame N)
- Clip-path animations
- Progress bars / deterministic counters
Use GSAP (gsap-animation skill) when:
- Text splitting (SplitText: chars/words/lines with mask)
- SVG stroke drawing (DrawSVG)
- SVG morphing (MorphSVG)
- Complex timeline orchestration with labels and position parameters
- ScrambleText decode effects
- Registered reusable effects
Note: @react-spring/web is NOT compatible with Remotion (it uses requestAnimationFrame internally). This skill uses Remotion's native spring() function which provides the same physics model in a frame-deterministic way.
Core API
spring()
Returns a value from 0 to 1 (can overshoot past 1 with low damping) based on spring physics simulation.
import { spring, useCurrentFrame, useVideoConfig } from 'remotion';
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const value = spring({
frame,
fps,
config: {
damping: 10,
stiffness: 100,
mass: 1,
},
});
Config Parameters
| Parameter | Range | Default | Effect |
|---|
damping | 1-200 | 10 | Resistance. Low = bouncy, high = smooth |
stiffness | 1-200 | 100 | Snap speed. High = fast, low = slow |
mass | 0.1-5 | 1 | Weight/inertia. High = sluggish, low = light |
overshootClamping | bool | false | Clamp at target (no overshoot) |
Additional Options
| Option | Type | Effect |
|---|
delay | number | Delay start by N frames (returns 0 until delay elapses) |
durationInFrames | number | Force spring to settle within N frames |
reverse | bool | Animate from 1 to 0 |
from | number | Starting value (default 0) |
to | number | Ending value (default 1) |
measureSpring()
Calculate how many frames a spring config takes to settle. Essential for <Sequence> and composition duration.
import { measureSpring } from 'remotion';
const frames = measureSpring({
fps: 30,
config: { damping: 10, stiffness: 100 },
});
Physics Presets
import { SpringConfig } from 'remotion';
export const SPRING = {
smooth: { damping: 200 } as Partial<SpringConfig>,
snappy: { damping: 20, stiffness: 200 } as Partial<SpringConfig>,
bouncy: { damping: 8 } as Partial<SpringConfig>,
heavy: { damping: 15, stiffness: 80, mass: 2 } as Partial<SpringConfig>,
wobbly: { damping: 4, stiffness: 80 } as Partial<SpringConfig>,
: { : , : } <>,
: { : , : , : } <>,
: { : , : , : } <>,
: { : , : } <>,
: { : , : , : } <>,
} ;
Preset Visual Reference
| Preset | Bounce | Speed | Feel | Best For |
|---|
smooth | None | Medium | Butter | Background, subtle reveals |
snappy | Minimal | Fast | Crisp | UI elements, buttons |
bouncy | Strong | Medium | Playful | Titles, icons, attention |
heavy | Small | Slow | Weighty | Dramatic reveals, large objects |
wobbly | Extreme | Medium | Cartoon | Playful, humorous |
stiff | Tiny | Very fast | Mechanical | Data viz, precise motion |
gentle | Minimal | Slow | Dreamy | Luxury, calm, organic |
molasses | Almost none | Very slow | Heavy | Cinematic, suspense |
pop | Strong | Fast | Punchy | Scale-in, badge, icon pop |
rubber | Extreme | Fast | Elastic | Exaggerated, cartoon, fun |
1. Spring Entrance Patterns
Basic Spring Entrance
import { spring, interpolate, useCurrentFrame, useVideoConfig, AbsoluteFill } from 'remotion';
import { SPRING } from './spring-presets';
const SpringEntrance: React.FC<{
children: React.ReactNode;
preset?: keyof typeof SPRING;
delay?: number;
}> = ({ children, preset = 'bouncy', delay = 0 }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = spring({ frame, fps, delay, config: SPRING[preset] });
const translateY = interpolate(progress, [0, 1], [60, 0]);
return (
<AbsoluteFill style={{
opacity: progress,
transform: `translateY(${translateY}px)`,
}}>
{children}
</AbsoluteFill>
);
};
Scale Pop
const ScalePop: React.FC<{
children: React.ReactNode;
delay?: number;
}> = ({ children, delay = 0 }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const scale = spring({ frame, fps, delay, config: SPRING.pop });
const opacity = spring({ frame, fps, delay, config: SPRING.smooth });
return (
<div style={{
transform: `scale(${scale})`,
opacity,
}}>
{children}
</div>
);
};
Enter + Exit (Spring Math)
const EnterExit: React.FC<{
children: React.ReactNode;
enterDelay?: number;
exitBeforeEnd?: number;
}> = ({ children, enterDelay = 0, exitBeforeEnd = 30 }) => {
const frame = useCurrentFrame();
const { fps, durationInFrames } = useVideoConfig();
const enter = spring({ frame, fps, delay: enterDelay, config: SPRING.bouncy });
const exit = spring({
frame, fps,
delay: durationInFrames - exitBeforeEnd,
config: SPRING.snappy,
});
const scale = enter - exit;
const opacity = enter - exit;
return (
<div style={{ transform: `scale(${scale})`, opacity }}>
{children}
</div>
);
};
2. Trail / Stagger Patterns
Spring Trail (staggered entrance)
Mimics React Spring's useTrail -- each element enters with a frame delay.
const SpringTrail: React.FC<{
items: React.ReactNode[];
staggerFrames?: number;
preset?: keyof typeof SPRING;
direction?: 'up' | 'down' | 'left' | 'right';
}> = ({ items, staggerFrames = 4, preset = 'bouncy', direction = 'up' }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const getOffset = (progress: number) => {
const distance = 50;
const remaining = interpolate(progress, [0, 1], [distance, 0]);
switch (direction) {
case 'up': return { transform: `translateY(${remaining}px)` };
case 'down': return { transform: `translateY(${-remaining}px)` };
case 'left': { : };
: { : };
}
};
(
);
};
Character Trail (text animation)
Manual character splitting with spring stagger. For advanced text splitting (mask reveals, line wrapping), use gsap-animation skill instead.
const CharacterTrail: React.FC<{
text: string;
staggerFrames?: number;
preset?: keyof typeof SPRING;
fontSize?: number;
}> = ({ text, staggerFrames = 2, preset = 'pop', fontSize = 80 }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
return (
<div style={{ display: 'flex', justifyContent: 'center', overflow: 'hidden' }}>
{text.split('').map((char, i) => {
const delay = i * staggerFrames;
const progress = spring({ frame, fps, delay, config: SPRING[preset] });
const translateY = interpolate(progress, [0, 1], [fontSize, 0]);
return (
<span key={i} style={{
display: 'inline-block',
fontSize,
fontWeight: 'bold',
color: '#fff',
opacity: progress,
transform: `translateY(${})`,
'',
}}>
{char === ' ' ? '\u00A0' : char}
);
})}
);
};
Word Trail
const WordTrail: React.FC<{
text: string;
staggerFrames?: number;
preset?: keyof typeof SPRING;
fontSize?: number;
}> = ({ text, staggerFrames = 5, preset = 'bouncy', fontSize = 64 }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const words = text.split(' ');
return (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.3em', justifyContent: 'center' }}>
{words.map((word, i) => {
const delay = i * staggerFrames;
const progress = spring({ frame, fps, delay, config: SPRING[preset] });
const scale = spring({ frame, fps, delay, config: SPRING.pop });
return (
<span key={i} style={{
display: 'inline-block',
fontSize,
fontWeight: 'bold',
color: '#fff',
,
`(${})`,
}}>
{word}
);
})}
);
};
Grid Stagger (center-out)
const GridStagger: React.FC<{
items: React.ReactNode[];
columns: number;
cellSize?: number;
gap?: number;
}> = ({ items, columns, cellSize = 120, gap = 16 }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const rows = Math.ceil(items.length / columns);
const centerCol = (columns - 1) / 2;
const centerRow = (rows - 1) / 2;
return (
<div style={{
display: 'grid',
gridTemplateColumns: `repeat(${columns}, ${cellSize}px)`,
gap,
}}>
{items.map((item, i) => {
const col = i % columns;
const row = Math.floor(i / columns);
// Distance from center determines delay
const dist = Math.sqrt((col - centerCol) ** 2 + (row - centerRow) ** 2);
const delay = Math.round(dist * 4);
const progress = spring({ frame, fps, delay, config: SPRING.pop });
return (
<div key={i} =
, ,
,
`(${})`,
}}>
{item}
);
})}
);
};
3. Chain / Sequence Patterns
Spring Chain (sequential animations)
Mimics React Spring's useChain -- animations trigger in sequence using measureSpring for timing.
import { spring, measureSpring, interpolate, useCurrentFrame, useVideoConfig } from 'remotion';
const SpringChain: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const step1Config = SPRING.bouncy;
const step1 = spring({ frame, fps, config: step1Config });
const step1Duration = measureSpring({ fps, config: step1Config });
const step2Delay = Math.round(step1Duration * 0.8);
const step2Config = SPRING.snappy;
const step2 = spring({ frame, fps, delay: step2Delay, config: step2Config });
const step2Duration = measureSpring({ fps, config: step2Config });
const step3Delay = step2Delay + step2Duration;
const step3 = spring({ frame, fps, delay: step3Delay, config: SPRING.gentle });
(
);
};
useSpringChain Hook
Reusable hook for chaining multiple springs with overlap control.
import { spring, measureSpring, SpringConfig, useCurrentFrame, useVideoConfig } from 'remotion';
type ChainStep = {
config: Partial<SpringConfig>;
overlap?: number;
};
function useSpringChain(steps: ChainStep[]) {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
let currentDelay = 0;
return steps.map((step, i) => {
if (i > 0) {
const prevDuration = measureSpring({ fps, config: steps[i - 1].config });
const overlap = step.overlap ?? 0;
currentDelay += Math.round(prevDuration * (1 - overlap));
}
return spring({ frame, fps, delay: currentDelay, config: step.config });
});
}
const [container, title, subtitle, cta] = useSpringChain([
{ config: . },
{ : ., : },
{ : ., : },
{ : ., : },
]);
4. Multi-Property Springs
Different physics per property
const MultiPropertySpring: React.FC<{ delay?: number }> = ({ delay = 0 }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const position = spring({ frame, fps, delay, config: SPRING.snappy });
const scale = spring({ frame, fps, delay, config: SPRING.bouncy });
const rotation = spring({ frame, fps, delay, config: SPRING.wobbly });
const opacity = spring({ frame, fps, delay, config: SPRING.smooth });
const translateX = interpolate(position, [0, 1], [-300, 0]);
const rotate = interpolate(rotation, [0, 1], [-15, 0]);
(
);
};
5. Spring Counter
Number counter with spring physics -- overshoots the target then settles.
const SpringCounter: React.FC<{
endValue: number;
prefix?: string;
suffix?: string;
preset?: keyof typeof SPRING;
delay?: number;
}> = ({ endValue, prefix = '', suffix = '', preset = 'bouncy', delay = 0 }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = spring({ frame, fps, delay, config: SPRING[preset] });
const value = Math.round(progress * endValue);
return (
<div style={{
fontSize: 96, fontWeight: 'bold', color: '#fff',
fontVariantNumeric: 'tabular-nums',
}}>
{prefix}{value.toLocaleString()}{suffix}
</div>
);
};
Comparison with linear counter:
interpolate() counter: smoothly reaches exact target, no overshoot
- Spring counter: overshoots then settles -- feels more energetic and alive
6. 3D Transform Patterns
Spring Card Flip
const SpringCardFlip: React.FC<{
frontContent: React.ReactNode;
backContent: React.ReactNode;
flipDelay?: number;
}> = ({ frontContent, backContent, flipDelay = 15 }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const flipProgress = spring({
frame, fps, delay: flipDelay,
config: { damping: 15, stiffness: 80 },
});
const rotateY = interpolate(flipProgress, [0, 1], [0, 180]);
return (
<AbsoluteFill style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div style={{ perspective: 800 }}>
<div style=
, , '',
'',
`(${})`,
}}>
{frontContent}
{backContent}
);
};
Perspective Tilt
const PerspectiveTilt: React.FC<{
children: React.ReactNode;
rotateX?: number;
rotateY?: number;
delay?: number;
}> = ({ children, rotateX = -20, rotateY = 15, delay = 0 }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = spring({ frame, fps, delay, config: SPRING.heavy });
const rx = interpolate(progress, [0, 1], [rotateX, 0]);
const ry = interpolate(progress, [0, 1], [rotateY, 0]);
const translateZ = interpolate(progress, [0, 1], [-200, 0]);
return (
<div style={{
perspective: 1000,
display: 'flex', alignItems: '', '',
}}>
{children}
);
};
7. Spring Transitions
Crossfade with Spring
const SpringCrossfade: React.FC<{
outgoing: React.ReactNode;
incoming: React.ReactNode;
switchFrame: number;
}> = ({ outgoing, incoming, switchFrame }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const outOpacity = frame < switchFrame ? 1 : 1 - spring({
frame: frame - switchFrame, fps, config: SPRING.smooth,
});
const inOpacity = frame < switchFrame ? 0 : spring({
frame: frame - switchFrame, fps, config: SPRING.smooth,
});
const inScale = frame < switchFrame ? 0.95 : interpolate(
spring({ frame: frame - switchFrame, fps, config: SPRING.bouncy }),
[0, 1], [0.95, 1]
);
return (
<AbsoluteFill>
<AbsoluteFill = }}>{outgoing}
{incoming}
);
};
Slide Transition with Spring
const SpringSlide: React.FC<{
outgoing: React.ReactNode;
incoming: React.ReactNode;
switchFrame: number;
direction?: 'left' | 'right' | 'up' | 'down';
}> = ({ outgoing, incoming, switchFrame, direction = 'left' }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = frame < switchFrame ? 0 : spring({
frame: frame - switchFrame, fps, config: SPRING.snappy,
});
const getTransform = (isOutgoing: boolean) => {
const offset = isOutgoing ? interpolate(progress, [0, 1], [0, -100]) : interpolate(progress, [0, 1], [100, 0]);
switch (direction) {
case 'left': return `translateX(%)`;
: ;
: ;
: ;
}
};
(
);
};
8. Templates
Spring Title Card
const SpringTitleCard: React.FC<{
title: string;
subtitle?: string;
accent?: string;
}> = ({ title, subtitle, accent = '#3b82f6' }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const bgScale = spring({ frame, fps, config: SPRING.heavy });
const words = title.split(' ');
const dividerWidth = spring({ frame, fps, delay: 8, config: SPRING.snappy });
const subtitleProgress = spring({ frame, fps, delay: 15, config: SPRING.gentle });
return (
<AbsoluteFill style={{
background: '#0f172a',
display: 'flex', '', '',
}}>
{/* Accent circle */}
{/* Title with word stagger */}
{words.map((word, i) => {
const delay = i * 4;
const progress = spring({ frame, fps, delay, config: SPRING.pop });
const y = interpolate(progress, [0, 1], [40, 0]);
return (
{word}
);
})}
{/* Divider */}
{/* Subtitle */}
{subtitle && (
{subtitle}
)}
);
};
Spring Lower Third
const SpringLowerThird: React.FC<{
name: string;
title: string;
accent?: string;
hold?: number;
}> = ({ name, title, accent = '#3b82f6', hold = 90 }) => {
const frame = useCurrentFrame();
const { fps, durationInFrames } = useVideoConfig();
const barIn = spring({ frame, fps, config: SPRING.snappy });
const nameIn = spring({ frame, fps, delay: 6, config: SPRING.bouncy });
const titleIn = spring({ frame, fps, delay: 10, config: SPRING.gentle });
const exitDelay = durationInFrames - 20;
const barOut = spring({ frame, fps, delay: exitDelay, config: SPRING.stiff });
const nameOut = spring({ frame, fps, : exitDelay - , : . });
titleOut = ({ frame, fps, : exitDelay - , : . });
(
);
};
Spring Feature Grid
const SpringFeatureGrid: React.FC<{
features: Array<{ icon: string; label: string }>;
columns?: number;
}> = ({ features, columns = 3 }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
return (
<AbsoluteFill style={{
display: 'flex', alignItems: 'center', justifyContent: 'center',
background: '#0f172a',
}}>
<div style={{
display: 'grid',
gridTemplateColumns: `repeat(${columns}, 200px)`,
gap: 32,
}}>
{features.map(({ icon, label }, i) => {
const delay = i * 5;
const scale = spring({ frame, fps, delay, config: SPRING.pop });
const opacity = spring({ frame, fps, delay, config: SPRING.smooth });
return (
<div key={i} style={{
textAlign: '', ,
'(,,,)', ,
`(${})`, ,
}}>
{icon}
{label}
);
})}
);
};
Spring Outro
const SpringOutro: React.FC<{
headline: string;
tagline?: string;
ctaText?: string;
accent?: string;
}> = ({ headline, tagline, ctaText, accent = '#3b82f6' }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const headlineProgress = spring({ frame, fps, config: SPRING.heavy });
const taglineProgress = spring({ frame, fps, delay: 12, config: SPRING.gentle });
const ctaProgress = spring({ frame, fps, delay: 20, config: SPRING.pop });
return (
<AbsoluteFill style={{
background: '#0f172a',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>
< = '' }}>
{headline}
{tagline && (
{tagline}
)}
{ctaText && (
{ctaText}
)}
);
};
9. Utility: useSpringTrail
Reusable hook for trail animations.
import { spring, SpringConfig, useCurrentFrame, useVideoConfig } from 'remotion';
function useSpringTrail(
count: number,
config: Partial<SpringConfig>,
staggerFrames = 4,
baseDelay = 0,
) {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
return Array.from({ length: count }, (_, i) => {
const delay = baseDelay + i * staggerFrames;
return spring({ frame, fps, delay, config });
});
}
const trail = useSpringTrail(5, SPRING.pop, 4);
10. Utility: useSpringEnterExit
Reusable hook for enter + exit pattern.
function useSpringEnterExit(
enterConfig: Partial<SpringConfig>,
exitConfig: Partial<SpringConfig>,
enterDelay = 0,
exitBeforeEnd = 30,
) {
const frame = useCurrentFrame();
const { fps, durationInFrames } = useVideoConfig();
const enter = spring({ frame, fps, delay: enterDelay, config: enterConfig });
const exit = spring({
frame, fps,
delay: durationInFrames - exitBeforeEnd,
config: exitConfig,
});
return enter - exit;
}
const progress = useSpringEnterExit(SPRING.bouncy, SPRING.stiff, 0, 25);
11. Combining with Other Skills
const CombinedScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const bgOpacity = spring({ frame, fps, config: SPRING.smooth });
return (
<AbsoluteFill>
{/* react-animation: visual atmosphere */}
<div style={{ opacity: bgOpacity }}>
<Aurora colorStops={['#3A29FF', '#FF94B4']} />
</div>
{/* spring-animation: bouncy title entrance */}
<SpringTitleCard title="Natural Motion" subtitle="Physics-driven beauty" />
{/* gsap-animation: text splitting that spring can't do */}
<GSAPTextReveal text="Advanced Typography" />
</AbsoluteFill>
);
};
| Skill | Best For |
|---|
| spring-animation | Bouncy entrances, elastic trails, organic physics, overshoot effects, spring counters |
| gsap-animation | Text splitting (SplitText), SVG drawing (DrawSVG), SVG morphing, complex timeline labels |
| react-animation | Visual backgrounds (Aurora, Silk, Particles), shader effects |
12. Composition Registration
export const RemotionRoot: React.FC = () => (
<>
<Composition id="SpringTitleCard" component={SpringTitleCard}
durationInFrames={90} fps={30} width={1920} height={1080}
defaultProps={{ title: 'SPRING PHYSICS', subtitle: 'Natural motion for video' }} />
<Composition id="SpringLowerThird" component={SpringLowerThird}
durationInFrames={180} fps={30} width={1920} height={1080}
defaultProps={{ name: 'Jane Smith', title: 'Creative Director' }} />
<Composition id="SpringFeatureGrid" component=
= = = =
= [
{ '🚀', '' },
{ '🎯', '' },
{ '✨', '' },
]}} />
);
13. Rendering
npx remotion render src/index.ts SpringTitleCard --output out/title.mp4
npx remotion render src/index.ts SpringTitleCard --codec h264 --crf 15
npx remotion render src/index.ts SpringTitleCard --codec gif --every-nth-frame 2
npx remotion render src/index.ts SpringTitleCard --codec prores --prores-profile 4444