Give drags physics
Release drags onto springs, dismiss on velocity, rubber-band edges.
From improve-animations and find-animation-opportunities.
Flick a sheet away
Flick each sheet down fast; only the right one reads speed.
Distance, 300ms tweenBad
Approve 3 bills
Velocity + springGood
Approve 3 bills
import { animate, motionValue } from "motion";
const SPRING = { type: "spring", duration: 0.5, bounce: 0.2 };
const y = motionValue(0);
y.on("change", (v) => { sheet.style.transform = `translateY(${v}px)`; });
function onRelease({ distance, elapsedMs, height }) {
const velocity = Math.abs(distance) / elapsedMs;
const dismiss = distance > height / 2 || (distance > 8 && velocity > 0.11);
animate(y, dismiss ? height : 0, { ...SPRING, velocity: y.getVelocity() });
}
Throw a card
Throw a card, then catch it mid-flight.
Fixed 300ms tween backBad
$4,210.00Drag me
animate(keyframes, 300ms ease)v0 = 0Spring with velocityGood
$4,210.00Drag me
spring 0.5s, bounce 0.2v0 = 0const SPRING = { type: "spring", duration: 0.5, bounce: 0.2 };
card.addEventListener("pointerdown", () => { x.stop(); y.stop(); });
card.addEventListener("pointerup", () => {
animate(x, 0, { ...SPRING, velocity: x.getVelocity() });
animate(y, 0, { ...SPRING, velocity: y.getVelocity() });
});
Scroll past the end
Drag or fling each list past its edge.
Recommended range 0.1 to 0.3, marked in green.
Hard stopBad
y = clamp(y, min, 0)Rubber bandGood
spring 0.5s, bounce 0.2const rubberband = (overshoot, dimension, c = 0.55) =>
(1 - 1 / ((overshoot * c) / dimension + 1)) * dimension;
const offset =
raw > 0 ? rubberband(raw, viewHeight) :
raw < min ? min - rubberband(min - raw, viewHeight) :
raw;
animate(y, edge, { type: "spring", duration: 0.5, bounce: 0.2, velocity });
Why it matters, pros and cons
Why it matters
A spring continues the finger’s speed. Edges that resist say “end”, not “frozen”.
Pros
- Short, fast flicks dismiss.
- Catch mid-flight with no restart.
- Boundaries explain themselves.
Cons
- Springs have no fixed end time.
- Velocity alone dismisses on a twitch; require travel.
- Native scroll already overscrolls; don't rebuild it.