Spring Physics
Spring animations overshoot their target and oscillate before settling, producing motion that feels genuinely physical in a way cubic-bezier curves cannot replicate.
A cubic-bezier curve always reaches its destination exactly on time. A spring overshoots, bounces back, and settles. The difference is the difference between a machine and a physical object.
How spring physics work
A spring animation is defined not by a duration and a curve, but by physical parameters: stiffness (how strong the spring is — higher stiffness means faster, snappier motion), damping (how quickly the oscillation dies out — higher damping settles faster with less overshoot), and mass (how heavy the object is — higher mass produces slower, more ponderous motion). The combination of these three values produces a curve that is not constrained to a fixed duration — it runs until the oscillation settles below a threshold.
Why it feels different
Cubic-bezier easing is a mathematical interpolation between two values over a fixed time. It is predictable and controllable, but its relationship to physics is only metaphorical. Spring physics is an actual simulation — the curve emerges from the physical properties of the system rather than being specified directly. This is why spring-animated elements feel like they have mass: because within the simulation, they do.
When to use springs
Springs are most appropriate for interactive, physical-feeling elements: a draggable component that snaps to a grid, a sheet that is pulled up and released, a button that bounces on press. They work best when the user’s interaction has a physical quality — pulling, throwing, pressing — and the spring’s response reinforces that quality. Springs feel wrong for informational animations — a tooltip appearing, a notification arriving — where the mechanical precision of a cubic-bezier is more appropriate.
CSS and library implementation
CSS cubic-bezier() cannot simulate spring overshoot — it is constrained to values between 0 and 1. The CSS linear() function (with many keyframe stops approximating a spring curve) can simulate springs in CSS, but the values must be pre-calculated. Most implementations use JavaScript animation libraries — Framer Motion, React Spring, Motion One — which provide first-class spring primitives. When using a library, prefer the spring API over pre-calculated keyframes so the physics parameters remain editable.
Tuning springs
The most common mistake with springs is excess bounce — an element that oscillates visibly before settling reads as playful or cartoon-like rather than physical. For most UI elements, aim for near-critical damping: a slight overshoot (5–10% past the target) before settling quickly. Reserve bouncier springs for explicitly playful contexts — games, onboarding, celebration moments — and use stiffer, more damped springs for professional application interfaces.
Gesture-driven animation
Gesture-driven animation is motion that tracks user input in real-time, rather than playing a pre-defined curve on a discrete event. The element’s position, scale, or opacity is directly tied to the user’s pointer or touch position — the animation is the gesture.
Common patterns:
- Swipe to dismiss — a list item follows the horizontal drag. If the drag reaches a threshold, a spring snaps it off-screen; otherwise, a spring snaps it back.
- Pull to refresh — a feed follows the vertical overscroll with increasing resistance. Release triggers a loading state or a spring snap back.
- Drag to reorder — an item follows the pointer while others shift to fill the gap. Release triggers a spring settle to the new position.
The critical distinction from click-triggered animation: the motion is continuous and bidirectional. The user can reverse direction mid-gesture and the animation must follow. Spring physics is essential here — a cubic-bezier curve with a fixed endpoint cannot follow a gesture that may reverse, pause, or release at any velocity.
Implementation uses pointer events to track position and directly sets transform values, then hands off to a spring animation on release with the pointer’s final velocity as the spring’s initial velocity:
element.addEventListener('pointerup', () => {
const velocity = getCurrentVelocity(); // px/ms at release
springAnimate({ to: 0, initialVelocity: velocity, stiffness: 200, damping: 25 });
});
Accessibility: every gesture-driven interaction must have a non-gesture alternative. Swipe-to-dismiss needs a delete button. Drag-to-reorder needs keyboard reordering. WCAG 2.5.1 requires that all path-based gesture functionality is also operable by single-pointer actions.
The takeaway
Use spring physics for interactions that have a physical quality — dragging, releasing, pressing. Keep damping high for most interfaces (minimal bounce). Use cubic-bezier for informational and navigational animations. Never reach for a spring just because it looks more interesting — match the physical quality of the animation to the physical quality of the interaction.