{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreifjscnxsstdob2nkyegzjdvop3h3zvurv46lsxa4q76i6wu44tvya",
"uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mpszbsjpw6g2"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreidbfjvupqywfyzlrwehttbtdjfyj2ngnjn6mdez7a4b3f3gd5vtf4"
},
"mimeType": "image/webp",
"size": 86130
},
"path": "/dev48v/spring-vs-tween-and-3-more-i-built-a-framer-motion-playground-with-the-code-beside-each-demo-5c3n",
"publishedAt": "2026-07-04T11:35:43.000Z",
"site": "https://dev.to",
"tags": [
"react",
"javascript",
"webdev",
"css",
"https://framer-motion-lab.vercel.app/",
"https://github.com/dev48v/framer-motion-lab"
],
"textContent": "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.\n\n**▶ Live demo:** https://framer-motion-lab.vercel.app/\n**Source (React 19 + Framer Motion 11):** https://github.com/dev48v/framer-motion-lab\n\n## The one insight worth the whole lab\n\nA **tween** has a fixed `duration` — it always takes the same time, regardless of distance:\n\n\n\n <motion.div animate={{ x: 260 }}\n transition={{ type: \"tween\", duration: 0.6, ease: \"easeInOut\" }} />\n\n\nA **spring** has _no duration_. It's physics — `stiffness`, `damping`, `mass` — and it settles naturally:\n\n\n\n <motion.div animate={{ x: 260 }}\n transition={{ type: \"spring\", stiffness: 300, damping: 18, mass: 1 }} />\n\n\nIn 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.\n\n## The other three, in one prop each\n\n**Gestures** — no state, no handlers:\n\n\n\n <motion.div whileHover={{ scale: 1.12, rotate: 3 }} whileTap={{ scale: 0.92 }} />\n\n\n**Drag** with a rubber-band edge:\n\n\n\n const area = useRef(null);\n <div ref={area}>\n <motion.div drag dragConstraints={area} dragElastic={0.2} />\n </div>\n\n\n**Stagger** — one variant definition orchestrates a whole list:\n\n\n\n const container = { show: { transition: { staggerChildren: 0.09 } } };\n const item = { hidden: { opacity: 0, y: 18 }, show: { opacity: 1, y: 0 } };\n <motion.ul variants={container} initial=\"hidden\" animate=\"show\">\n {items.map(t => <motion.li key={t} variants={item} />)}\n </motion.ul>\n\n\nThat's the part that surprises people: the parent's `staggerChildren` sequences the children automatically — you don't compute delays per item.\n\nEvery 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",
"title": "Spring vs Tween, and 3 More: I Built a Framer Motion Playground With the Code Beside Each Demo"
}