Springs for gestures
Spring anything dragged or interrupted; keep bounce 0.1 to 0.3.
Emil Kowalski@emilkowalskiNotice how when I drag it to dismiss there's a slight bounce at the end, but when I simply press the close button, there's no bounce at all. A drag requires some force, so adding a subtle bounce makes this transition feel more natural.
Emil Kowalski@emilkowalskiswapped spring animaton for an ease-out one so it feels less abrupt.
Emil Kowalskiemilkowal.skiinterpolates value changes with spring-like behavior, rather than updating them immediately
From emil-design-eng, “Spring Animations”.
1. Interrupt it
Press Toggle twice quickly, or Interrupt at 150ms.
CSS keyframesBad
animation: 500ms var(--ease-out)CSS transitionOkay
transition: transform 500msSpringGood
spring({ duration: 0.5, bounce: 0.2 })<motion.div
animate={{ x: open ? travel : 0 }}
transition={{ type: "spring", duration: 0.5, bounce: 0.2 }}
/>
2. Follow the pointer
Move the pointer over both cards.
Raw valueBad
rotate = pointerX * 0.1Spring-interpolatedGood
useSpring(pointerX * 0.1, { stiffness, damping })
bounce ≈ 0.50
import { useSpring } from "motion/react";
const rotateY = useSpring(pointerX * 0.1, { stiffness: 100, damping: 10 });
3. Drag versus button
Flick each sheet, then use its button.
One transitionBad
transform 400ms easeSpring on dragGood
drag: spring · button: 300msdragbutton
function onRelease(velocity) {
animate(y, target, { type: "spring", duration: 0.5, bounce: 0.2, velocity });
}
function onButtonPress() {
animate(y, target, { duration: 0.3, ease: [0.32, 0.72, 0, 1] });
}
Why it matters, pros and cons
Why it matters
A spring carries velocity, so a new target bends the path instead of restarting.
Pros
- Interruptions stay continuous.
- A flick's velocity flows into the settle.
duration+bouncereads clearly.
Cons
- Modals and dialogs usually want ease-out instead.
- Button presses carry no force; bounce reads as wobble.
- Functional data should update directly, not spring.