Writing

Spring, Mass, and Motion

science behind animations that feel less like code, and more like intuition

Physics was always my first love in school - there was something magical about how it could explain everything around us – from why leaves fall to why the sky is blue.

As a developer who's been extensively using Framer Motion for the past few months, I've fallen in love with its spring animations. It's a beautiful marriage of my two passions: physics and programming.

While implementing these animations across various UI components in my projects, I noticed that many developers, including myself initially, weren't fully aware of what parameters like mass, stiffness, and damping actually meant.

Thanks to my interest in physics, I can help demystify these concepts and show how the principles I learned back in school create these smooth, natural-looking animations.

I'm here for balance.

Hooke's Law: The Science of Springs

At its core, a spring animation follows the physics of what scientists call a Harmonic Oscillator. Don't let this term intimidate you – it's simpler than it sounds!

Back in school, we defined a Harmonic Oscillator like this:

A Harmonic Oscillator is a system that oscillates with a sinusoidal motion. In the context of a spring, this means that the spring will oscillate back and forth around its equilibrium position.

The motion of a spring can be described by the following equation:

F=k×xF = -k \times x
Equation Glossary
FF - the amount of force applied
kk - stiffness (how resistant the spring is)
xx - displacement from its resting point

This is the basic principle of a spring animation.

But wait, there’s more. Do you remember the three laws of motion?

I'm here for balance.

Newton's 2nd law of motion states that

The acceleration of an object is directly proportional to the net force acting on it and inversely proportional to its mass.

i.e.    F=m×ai.e. \;\; F = m \times a
Equation Glossary
FF - the force applied to the spring
mm - mass (how heavy the object is)
aa - acceleration (how fast the object is moving)

By combining these two formulas, we get:

m×a=k×xm \times a = -k \times x

Rearranging the equation, we get the acceleration:

a=k×xm\Rightarrow a = -\frac{k \times x}{m}

i.e.

acceleration (aa) = stiffness×displacementmass-\frac{\text{stiffness} \times \text{displacement}}{\text{mass}}

This means the spring's acceleration depends on its stiffness, displacement, and mass. From acceleration, we can figure out:

  • Velocity (vv): How fast the object is moving.
  • Position (pp): Where the object is at any point in time.

Wondering how? See if you can think of an answer, before scrolling further...

I'm here for balance.

Kinematics!

While Newton's 2nd law gives us the cause of acceleration (force), kinematic equations like v=u+a×tv = u + a \times t tell us how velocity and position change as a result.

v=u+a×tv = u + a \times t
Equation Glossary
vv - velocity
uu - initial velocity
aa - acceleration
tt - time

and similarly,

p=p0+v×tp = p_0 + v \times t
Equation Glossary
pp - position
p0p_0 - initial position
vv - velocity
tt - time

In both of the above equations, tt is the time interval between frames, which in the world of frontend animations is tied to the frame rate. For Framer Motion, this is typically 60 frames per second,

so t=160t = \frac{1} {60} or roughly 0.01666 seconds.

I'm here for balance.

Let's see how these physics equations translate into code.

Here's a function that calculates object positions over time based on spring motion:

simulation.js
1const calculateSpringMotion = (stiffness, mass, duration) => { 2 3 /* Length of the spring, let's assume it's 1 for simplicity */ 4 const SPRING_LENGTH = 1; 5 6 const FRAME_RATE = 1 / 60; // i.e. 60 FPS 7 8 /* The total number of frames we want to generate */ 9 const TOTAL_FRAMES = duration * FRAME_RATE; 10 11 let x = 5; // Initial position of the object 12 let v = 0; // Initial velocity is 0 13 14 /* Spring constant */ 15 const k = -stiffness; // Negative as spring force opposes displacement 16 17 /* Initiate the array of position*/ 18 const positions = []; 19 20 /* Calculate position for each frame */ 21 for (let i = 0; i < TOTAL_FRAMES; i++) { 22 const force = k * (x - SPRING_LENGTH); 23 const a = force / mass; 24 25 v += a * FRAME_RATE; 26 x += v * FRAME_RATE; 27 28 positions.push(x); 29 } 30 31 return positions; 32 33};

I'm here for balance.

To make this interactive, I’ve created a playground below where you can experiment with the spring animation parameters. Adjust the mass and stiffness, to observe how the animation changes in real-time : )


Spring Motion Simulation

Want to build this?

Just pass these props to a motion.div — that's it!

1<motion.div 2 className="w-16 h-16 bg-indigo-600 rounded-full shadow-md" 3 initial={{ x: "-200%" }} 4 animate={{ x: "0%" }} 5 transition={{ type: "spring", stiffness, mass}} 6 />

I'm here for balance.

Important thing to note

One of the things you might’ve noticed is that the animation never stops.

This actually, makes perfect sense from a physics perspective. In real life, In the real world, springs don’t just stop. They keep moving,subtly, endlessly until something slows them down or i.e. the energy dissipates.

However, in real life, the energy dissipates due to friction. In Framer Motion, we don’t have friction, so the animation never stops.



In order to simulate friction,

we need to add damping to the spring.

Damping is a force that opposes the motion of an object. In the context of a spring, it means that the spring will lose energy over time and eventually stop oscillating.

The formula for damping is:

F=b×vF = -b \times v
Equation Glossary
FF - the amount of force applied
bb - damping (how much the spring is damped)
vv - velocity (how fast the spring is moving)

Taking this into account, our total force equation becomes:

F=Fspring+FdampingF = F_{spring} + F_{damping}
F=k×xb×v\Rightarrow F = -k \times x - b \times v

This means that the acceleration equation becomes:

a=k×xb×vma = \frac{-k \times x - b \times v}{m}

I'm here for balance.

Let’s make these changes in our code:

Function to simulate the physics of a spring using Hooke's Law and Newton's Second Law + Damping Force
1const calculateSpringMotion = (stiffness, mass, damping, duration) => { 2 3 /* Length of the spring, let's assume it's 1 for simplicity */ 4 const SPRING_LENGTH = 1; 5 6 const FRAME_RATE = 1 / 60; // i.e. 60 FPS 7 8 /* The total number of frames we want to generate */ 9 const TOTAL_FRAMES = duration * FRAME_RATE; 10 11 let x = 5; // Initial position of the object 12 let v = 0; // Initial velocity is 0 13 14 /* Spring constant */ 15 const k = -stiffness; // Negative as spring force opposes displacement 16 17 /* Damping Constant */ 18 const d = -damping; 19 20 /* Initiate the array of position*/ 21 const positions = []; 22 23 /* Calculate position for each frame */ 24 for (let i = 0; i < TOTAL_FRAMES; i++) { 25 26 /* The one we calculated earlier was just the spring force */ 27 const forceSpring = k * (x - SPRING_LENGTH); 28 const forceDamping = d * v; 29 30 const a = (forceDamping + forceSpring) / mass; 31 32 v += a * FRAME_RATE; 33 x += v * FRAME_RATE; 34 35 positions.push(x); 36 } 37 38 return positions; 39 40};

I'm here for balance.

Let's add another control damping to our playground and update the calculateSpringMotion function.

Spring Motion Simulation (with damping force)

The secret sauce?

Just add another damping parameter to the transition prop in motion.div

1<motion.div 2 className="w-16 h-16 bg-indigo-600 rounded-full shadow-md" 3 initial={{ x: "-200%" }} 4 animate={{ x: "0%" }} 5 transition={{ type: "spring", stiffness, mass, damping}} 6 />

I'm here for balance.

Observation

As you can see, the spring animation gradually comes to a halt as damping dissipates energy from the system.

This is reflected in the chart, where the motion converges toward a final “resting position.”

By increasing the damping slider to a higher value, you’ll notice that the object reaches this “resting position” much faster compared to when the damping value is lower.

I'm here for balance.

In Motion (formerly framer-motion), these physics principles are abstracted away, but understanding them helps in creating better animations.

Here’s an example of different states in a dynamic island component (credits to Emil Kowalski) just to give you a sense of what you can do with these physics principles :)


Dynamic Island spring animation example

import { useMemo, useState } from "react";
import { motion } from "motion/react";

import { Ring } from "./ring";
import { Timer } from "./timer";

export default function DynamicIsland() {

// State
const [view, setView] = useState("idle");

const content = useMemo(() => {
switch (view) {
  case "ring":
    return <Ring />;
  case "timer":
    return <Timer />;
  case "idle":
    return <div className="h-7" />;
}
}, [view]);

return (
<div className="h-screen flex flex-col items-center justify-center p-4">
<div className="flex h-[100px] justify-center">
  <motion.div
    layout
    style={{ borderRadius: 9999 }}
    className="h-fit min-w-[100px] overflow-hidden bg-black"
  >
    {content}
  </motion.div>
</div>
<div className="flex justify-center gap-4">
  <button
    type="button"
    className="rounded-full w-16 h-10 bg-white px-2.5 py-1.5 text-sm font-medium text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
    onClick={() => setView("idle")}
  >
    Idle
  </button>
  <button
    type="button"
    className="rounded-full w-16 h-10 bg-white px-2.5 py-1.5 text-sm font-medium text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
    onClick={() => setView("ring")}
  >
    Ring
  </button>
  <button
    type="button"
    className="rounded-full w-16 h-10 bg-white px-2.5 py-1.5 text-sm font-medium text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
    onClick={() => setView("timer")}
  >
    Timer
  </button>
</div>
</div>
); }

I'm here for balance.

The default values in Motion (formely framer-motion) stiffness: 100, damping: 10, mass: 1 work well for most cases, but now that you understand what each parameter does, you can fine-tune them to achieve exactly the animation feel you’re looking for ✨

I hope this helps you understand the physics behind spring animations and how you can use it to create better animations.

I'm here for balance.

P.S. So it turns out that, the same laws that made leaves fall in school,
now help buttons bounce, cards glide, and interfaces breathe :)


last updated on May 4, 2025