The physics behind spring animations

Jun 23 2020

/ 4 min read /

0 Likes

0 Replies

0 Reposts

In the past few months, I've become a big fan of Framer Motion. After looking at how I could use it to animate my styled-components, I've been tinkering around spring animations and rebuilt almost all the transitions and animations of components in several UI projects. While showcasing the result to some fellow developers I got some questions around the meaning of some of the terms and options used to set up a spring animation like mass, stiffness, and damping. Most of them were setting them without really knowing how they were influencing the resulting animation. Lucky for them, I used to study maths and physics in college and was able to bring light on the physics behind this type of animation.

This article aims to explain how a spring animation in a library like Framer Motion works, the laws of physics that are behind it, and the relation between the different options you can set for a spring animation.

This is the very first time I'm using something I've learned in physics class in college 😅, everything is possible!

Also, I only studied this subject in french and never wrote about it in english, if there are any misused terms or definitions, please let me know and I'll update the post with the appropriate fixes.

Hooke's Law

First of all, a spring animation has this name because the animation itself follows the physics of a spring or what we also call a Harmonic Oscillator. This term and the math surrounding it might seem very scary and complicated but bare with me, I'll break down everything as simply as possible. When I was in college, we defined a Harmonic Oscillator as follows:

a system that experiences a force (F) proportional to a displacement x when displaced from its equilibrium.

The formula for such force is called Hooke's Law and it is defined as follows:

1
F = -k*x

where k is a positive constant called stiffness which we can also write as:

force = negative stiffness * displacement

What that means is that:

  • if we pull the spring (i.e. x > 0 ) to a certain distance away from its equilibrium, it will start to move
  • if we don't pull it, it won't move (i.e. x = 0)

However, maybe you might have heard at school or on one of the many science-focused Youtube channels on that force is the object's mass times its acceleration, which translates to the following formula:

1
F = m*a

where m is the mass and a is the acceleration.

Thus given this formula and the formula above, we can deduct that:

1
m*a = -k*x

which is equivalent to

1
a = -k *x / m

acceleration = negative stiffness * displacement / mass

We now have an equation from which we define the acceleration based on the displacement of our spring and the mass of the object attached to that spring. From the acceleration we can deduct the following:

  • the velocity of the object at any given time
  • the position of the object at any given time

To get the velocity of the object, you need to add the acceleration rate to the previously recorded velocity, which can translate to the following equation:

1
v2 = v1 + a*t

velocity = old velocity + acceleration * time interval

Finally, we can get the position as it follows a similar principle: the position of the object is equal to the previously recorded position to which we add the velocity:

1
p2 = p1 + v*t

position = old position + velocity * time interval

For the time interval, as frontend developers, we might know it better as a frame rate or "frames per second". Considering the smoothness of Framer Motion's animations we can assume that its spring animations runs at 60 frames per second thus a time interval that is constant and equal to 1/60 or 0.01666.

Translating the maths to Javascript

Now that we've done the math, you can see that by knowing the mass of the object, the stiffness and the displacement of our spring, we can know the position of the object attached to that spring at any given time, i.e at any given frame. We can translate all the equations above in Javascript, and for a given displacement calculate all the positions of an object for 600 frames, i.e. 10 seconds:

Function that returns the positions of an object following the motion of a spring

js
1
const loop = (stiffness, mass) => {
2
/* Spring Length, set to 1 for simplicity */
3
let springLength = 1;
4
5
/* Object position and velocity. */
6
let x = 2;
7
let v = 0;
8
9
/* Spring stiffness, in kg / s^2 */
10
let k = -stiffness;
11
12
/* Framerate: we want 60 fps hence the framerate here is at 1/60 */
13
let frameRate = 1 / 60;
14
15
/* Initiate the array of position and the current framerate i to 0 */
16
let positions = [];
17
let i = 0;
18
19
/* We loop 600 times, i.e. for 600 frames which is equivalent to 10s*/
20
while (i < 600) {
21
let Fspring = k * (x - springLength);
22
23
let a = Fspring / mass;
24
v += a * frameRate;
25
x += v * frameRate;
26
27
i++;
28
29
positions.push({
30
position: x,
31
frame: i,
32
});
33
}
34
35
/**
36
* positions is an array of number where each number
37
* represents the position of the object in a spring
38
* motion at a specific frame
39
*
40
* We use this array to plot all the position of the
41
* object for 10 seconds.
42
*/
43
return positions;
44
};

I built this small playground below with a graph representation of the positions that are returned by the function above a component animated by Framer Motion that has the same mass and stiffness. You can tune the mass and the stiffness with the range inputs above the graph and observe how each variable influences the animated component and the graph of positions.

Taking damping into account

While observing the visualization above, you might have wondered why is the spring animation never-ending as opposed to the ones you might have tried yourself with Framer Motion. That is because the math formulas we used to generate the position of the object were not taking into account friction and heat. If we want to obtain a spring animation that feels natural, we should see the movement of the object slowing down as time passes to eventually stop moving. That is where the damping comes into the picture. You might have seen this term when looking at the documentation of Framer Motion and wondered what it meant or does to the resulting spring animation, here's how we will define it:

Damping is the force that slows down and eventually stops an oscillation by dissipating energy

Its formula is:

1
Fd = -d * v

where d is the damping ratio and v the velocity

damping Force = negative damping * velocity

Taking damping into account will bring some changes to the acceleration formula we established in the first part. We know that

1
F = m*a

However, F here is equal to the spring force and the damping force, instead of just the spring force, thus:

1
Fs + Fd = m*a -> a = (Fs + Fd)/m

We can now add this new formula to the Javascript code we've showcased in the previous part (I highlighted the additions I've made to the code compared to the previous implementation):

Updated function that takes into account the damping ratio

js
1
const loop = (stiffness, mass, damping) => {
2
/* Spring Length, set to 1 for simplicity */
3
let springLength = 1;
4
5
/* Object position and velocity. */
6
let x = 2;
7
let v = 0;
8
9
/* Spring stiffness, in kg / s^2 */
10
let k = -stiffness;
11
12
/* Damping constant, in kg / s */
13
let d = -damping;
14
15
/* Framerate: we want 60 fps hence the framerate here is at 1/60 */
16
let frameRate = 1 / 60;
17
18
let positions = [];
19
let i = 0;
20
21
/* We loop 600 times, i.e. for 600 frames which is equivalent to 10s*/
22
while (i < 600) {
23
let Fspring = k * (x - springLength);
24
let Fdamping = d * v;
25
26
let a = (Fspring + Fdamping) / mass;
27
v += a * frameRate;
28
x += v * frameRate;
29
30
i++;
31
32
positions.push({
33
position: x,
34
frame: i,
35
});
36
}
37
38
return positions;
39
};

And finally, represent the resulting position data in the playground:

As you can see we now have a spring animation that eventually stops due to damping dissipating the energy out of the system. The chart above showcases this by converging towards a final "resting position". By increasing the damping slider to a high value you can observe that the object subject to spring animation tends to converge to the "resting position" way faster than for a lower damping value.

A real-life example

By default, Framer Motion sets the stiffness of the spring animation to 100, the damping to 10, and the mass to 1 according to the docs. Below, I wrote an animated Button component that is closer to a real-life example that you might want to implement in your UI projects. Now that you know what mass, stiffness and damping you can try to fine-tune your spring animation.

To keep this article short, I omitted some other options that Framer Motion provides for spring animations such as:

  • velocity: I considered the initial velocity to be equal to 0 in our examples above
  • restSpeed
  • restDelta

They are all defined in the documentation, and I invite you to add them to the playground above to see how they influence the resulting animation.

Fetching Replies...

Do you have any questions, comments or simply wish to contact me privately? Don’t hesitate to shoot me a DM on Twitter.


Have a wonderful day.
Maxime


© 2020 Maxime Heckel —— Made in SF. Polished in NY.