Skip to main content

Transitions & Timing: Tween vs Spring Animation Physics

The transition prop controls how Framer Motion animates between states. It determines whether an animation uses tween (linear time-based) or spring (physics-based) behavior, and how long the animation takes. Choosing the right transition type is the difference between stiff, predictable motion and natural, responsive animation.

I've shipped motion with both types in production: tweens for precise, time-synced sequences (header slides), springs for interactive feedback (button presses, card flips). This article covers both, when to use each, and the performance implications.

What Are Tweens and Springs?

A tween is a time-based animation: you specify a duration (e.g., 0.5 seconds) and easing curve (linear, ease-in, ease-out), and the animation completes in that exact time. Tweens are predictable and easy to synchronize with other animations.

A spring is a physics-based animation: you configure stiffness and damping, and the animation follows Hooke's law (like pulling a mass attached to a spring). Springs feel natural—they bounce, overshoot slightly, and settle—and they're responsive to interruptions (if the target changes mid-animation, the spring re-targets smoothly).

Using Tween Animations

Tween animations require a duration (in seconds) and an optional ease function:

import { motion } from 'framer-motion';

export function TweenExample() {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{
duration: 0.8,
ease: 'easeInOut' // or 'easeIn', 'easeOut', 'linear', 'circInOut'
}}
style={{
width: '100px',
height: '100px',
backgroundColor: '#3498db',
borderRadius: '8px'
}}
>
Tween Animation
</motion.div>
);
}

Framer Motion supports these built-in easing curves: 'linear', 'easeIn', 'easeOut', 'easeInOut', 'circIn', 'circOut', 'circInOut', 'backIn', 'backOut', 'backInOut', 'anticipate'. You can also provide a custom easing function (e.g., from eases package) or use cubic-bezier values.

Common Tween Patterns

DurationUse CaseExample
0.2–0.3sMicro-interactions (button feedback, icon switches){ duration: 0.25, ease: 'easeOut' }
0.5–0.8sComponent entrance animations, card reveals{ duration: 0.6, ease: 'easeInOut' }
1–2sPage transitions, hero animations{ duration: 1.2, ease: 'easeInOut' }

Fast tweens feel snappy; slow tweens feel luxurious but can feel sluggish if overused. Most polished UIs use 0.3–0.6 second tweens as the baseline.

Using Spring Animations

Spring transitions are defined by stiffness (how tight the spring is) and damping (how much the motion oscillates). You optionally set mass (how heavy the animated object is):

import { motion } from 'framer-motion';

export function SpringExample() {
return (
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{
type: 'spring',
stiffness: 100,
damping: 10,
mass: 1
}}
style={{
width: '100px',
height: '100px',
backgroundColor: '#e74c3c',
borderRadius: '50%'
}}
>
Spring Animation
</motion.div>
);
}

Higher stiffness makes the spring pull harder (faster, more bouncy). Higher damping makes it settle faster (less oscillation). A mass of 1 is typical; increase it for heavier-feeling motion, decrease for lighter.

Framer Motion provides preset spring configurations:

transition={{ type: 'spring', ...Framer.presets.stiff }} // Fast, bouncy
transition={{ type: 'spring', ...Framer.presets.molasses }} // Slow, heavy

But defining your own values gives you control. Here's a practical spring setup used in production UI:

transition={{
type: 'spring',
stiffness: 300, // Responsive, snappy
damping: 30, // Settle quickly, minimal bounce
mass: 0.8 // Slightly lighter than default
}}

Tween vs Spring: Decision Guide

ScenarioUse TweenUse Spring
Sequential animations synced to timeline
Interactive feedback (hover, click, drag)
Page entrance animations
Button press feedback
List stagger reveal
Draggable element release

Tweens excel when you need predictable, synchronized motion. Springs excel when you need responsive, natural feedback that adapts to user input.

Adding Delay to Animations

Use the delay property to stagger animations over time:

import { motion } from 'framer-motion';

export function StaggeredList() {
const items = ['Item 1', 'Item 2', 'Item 3'];

return (
<ul style={{ listStyle: 'none', padding: 0 }}>
{items.map((item, i) => (
<motion.li
key={i}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{
duration: 0.4,
delay: i * 0.1 // First item: 0s, second: 0.1s, third: 0.2s
}}
style={{
padding: '10px',
borderBottom: '1px solid #eee'
}}
>
{item}
</motion.li>
))}
</ul>
);
}

Each list item animates after the previous one, creating a cascade effect. Adjust the multiplier (0.1) to change the stagger timing.

Transition Inheritance and Overrides

When using variants (covered in the next article), motion components inherit transition settings from parent to child unless overridden:

import { motion } from 'framer-motion';

const containerVariants = {
animate: {
transition: {
staggerChildren: 0.1
}
}
};

const childVariants = {
initial: { opacity: 0 },
animate: { opacity: 1 }
};

export function SequencedList() {
return (
<motion.ul
variants={containerVariants}
initial="initial"
animate="animate"
style={{ listStyle: 'none' }}
>
{['A', 'B', 'C'].map((letter) => (
<motion.li
key={letter}
variants={childVariants}
transition={{
duration: 0.3, // Override container's transition
ease: 'easeOut'
}}
style={{ padding: '8px' }}
>
{letter}
</motion.li>
))}
</motion.ul>
);
}

The container's transition settings propagate to children, but each child's transition prop overrides as needed.

Performance: Why Duration Matters at Scale

Animation performance depends on the browser's refresh rate (typically 60fps, or 16.67ms per frame). A 0.3-second animation requires at least 18 frames. If your animation is too fast (< 0.1s), it may skip frames on slower devices; too slow (> 2s) feels sluggish.

For 60fps animations, Framer Motion uses CSS transforms (translate, scale, rotate, opacity) which are GPU-accelerated. Avoid animating layout properties (width, height, position) because they trigger reflow—use transform: scale instead.

Test animations on real devices: 0.3–0.6 second animations are safest across modern and older hardware.

Key Takeaways

  • Tween animations use a fixed duration and easing curve; they're predictable and ideal for sequences.
  • Spring animations use physics (stiffness, damping); they feel natural and are responsive to interruptions.
  • Use tweens for choreographed motion (page transitions, list reveals); use springs for interactive feedback (hover, click).
  • Combine duration and ease in tweens, or stiffness, damping, and mass in springs.
  • Apply delay to stagger animations; combine with transition.staggerChildren in variants for complex sequences.

Frequently Asked Questions

What's the difference between transition.duration and animation speed?

duration is the total time in seconds for the animation to complete. Speed depends on distance: animating from 0 to 100px over 0.5s is faster per pixel than animating 0 to 50px over the same duration. Framer Motion calculates speed automatically.

Can I use spring animations in sequences where I need exact timing?

Springs are less predictable in duration because they depend on the target, stiffness, and damping. For precise, synchronized sequences (e.g., a music-synced animation), use tweens. Combine springs with delay carefully, as the total time is approximate.

How do I know if an animation is hitting 60fps?

Open your browser's DevTools Performance tab, record an animation, and check for consistent 16.67ms frame timing. If frames drop or jank appears, reduce animation complexity or shorten duration. Framer Motion's use of CSS transforms usually ensures 60fps.

Can I transition between different transition types (tween to spring)?

Not directly in a single animation. But you can change the transition prop between states: animate with tween on entrance, spring on interaction. Framer Motion handles the transition smoothly.

Further Reading