DEV Community

Devanshu Biswas
Devanshu Biswas

Posted on

Spring vs Tween, and 3 More: I Built a Framer Motion Playground With the Code Beside Each Demo

The Framer Motion docs list every prop, but they can't make you feel the difference between a spring and a tween, or what stiffness and damping actually do to a motion. So I built four live demos with the code next to each — drag the sliders, fling the box, and it lands.

▶ Live demo: https://framer-motion-lab.vercel.app/
Source (React 19 + Framer Motion 11): https://github.com/dev48v/framer-motion-lab

The one insight worth the whole lab

A tween has a fixed duration — it always takes the same time, regardless of distance:

<motion.div animate={{ x: 260 }}
  transition={{ type: "tween", duration: 0.6, ease: "easeInOut" }} />
Enter fullscreen mode Exit fullscreen mode

A spring has no duration. It's physics — stiffness, damping, mass — and it settles naturally:

<motion.div animate={{ x: 260 }}
  transition={{ type: "spring", stiffness: 300, damping: 18, mass: 1 }} />
Enter fullscreen mode Exit fullscreen mode

In the lab you drag those three sliders and watch the box go from bouncy (underdamped, damping ratio ζ < 1) to snappy (critically damped) to sluggish (overdamped) — with the live ζ shown. The reason springs win for anything interactive: they handle interruptions gracefully. Grab a spring mid-animation and it flows to the new target; a tween has to restart its clock.

The other three, in one prop each

Gestures — no state, no handlers:

<motion.div whileHover={{ scale: 1.12, rotate: 3 }} whileTap={{ scale: 0.92 }} />
Enter fullscreen mode Exit fullscreen mode

Drag with a rubber-band edge:

const area = useRef(null);
<div ref={area}>
  <motion.div drag dragConstraints={area} dragElastic={0.2} />
</div>
Enter fullscreen mode Exit fullscreen mode

Stagger — one variant definition orchestrates a whole list:

const container = { show: { transition: { staggerChildren: 0.09 } } };
const item = { hidden: { opacity: 0, y: 18 }, show: { opacity: 1, y: 0 } };
<motion.ul variants={container} initial="hidden" animate="show">
  {items.map(t => <motion.li key={t} variants={item} />)}
</motion.ul>
Enter fullscreen mode Exit fullscreen mode

That's the part that surprises people: the parent's staggerChildren sequences the children automatically — you don't compute delays per item.

Every demo is live and the code updates with your settings. If it made Framer Motion click, a star helps others find it: https://github.com/dev48v/framer-motion-lab

Top comments (0)