I was working on some prototypes which required me to animate properties which were not supported by UIKit animators. So I was left with no choice but to build my own animator.
No worthy animator exists without springs. And I knew nothing about springs beyond "they are bouncy!". I had a lot to learn.
I remember looking at those properties of a spring (mass, stiffness, damping, velocity) and had very little idea of what they meant. Creating a spring was more of a guessing game inputting numbers randomly until I got something that worked.
Well let me tell you, it took a LOT of googling to understand these damn, I mean awesome, things! Most articles were super advanced and assumed a greater understanding of math than I currently have. So I wanted to try to write my own post, targeted at designers and developers who like me might not have prior understanding of these principles.
I’m going to try to get as basic as possible, which at times may be too basic for some of you.
I’m also going to explain why stiffness and mass don’t need to be manually set, but it helps to understand springs in order to understand why that is.
Hooke’s Law / Harmonic Oscillators
First of all, let’s forget about the spring in the real world. The springs we know have a size, a thickness and often have something attached at the end of them.
But in mathematical terms I spring is what’s called a Harmonic Oscillator. Basically an infinite wavy curve like this.
To “draw” this curve with math you obviously need to plot the position over time.
So let’s break that down.
If we're drawing a wavy line, it means there a change in velocity going on - acceleration and deceleration. And if there’s acceleration then there’s gotta be a force making it go faster and slower.
A resting spring has no force, so you need to “activate” it by pulling it. How far you pull is called our displacement.
Now that it’s pulled, there’s a force pulling it back to its original position. There’s a special formula to get the force of a spring based on Hooke’s Law.
F = -kx
k is a stiffness coefficient
x is the displacement value (how far the object moved from previous value)
Force = Negative Stiffness Value * Displacement value
Notice that mass is not a part of this force. All we need to define a spring’s force is how stiff it is.
We have the force, so we can calculate the acceleration.
The formula for acceleration is:
a = F / m
Acceleration = Force / Mass
The mass will be a constant, say 1, since we’re assuming our object is not changing weights.
That means our acceleration is:
a = (-kx) / m
Acceleration = (Negative Stiffness × Displacement) / Mass
Now we know the acceleration, we can calculate the velocity of our object at any given time.
To accelerate velocity you need to add the acceleration rate to the previous velocity.
v1 = v0 + a × t
New Velocity = Previous Velocity + (Acceleration × Time Interval)
The time interval when it comes to computers is normally set in frames per second (hertz).
60 frames per second = 1/60 = 0.016666 seconds
Finally, you can calculate the new position.
p1 = p0 + v × t
New Position = Old Position + New Velocity × Time Interval
Do you see how that never-ending wave is formed? The displacement (x) affects our force since F = -kx. So that means that the force will change based on how far it’s displaced, and if the force changes the acceleration changes too, and if the acceleration changes the velocity changes and the displacement changes, which affects the force… 🤯
All it means is that this will go on forever making the oscillator curve with nothing to stop it. Which is obviously not what we want in an animation.
And that’s where damping comes in.
In nature, no spring is perfect like that. There are forces at play like friction and heat. So the springs we are familiar with always stop at some point. That stopping force is called damping.
The damping force formula is:
F = -dv
Force = Negative Damping coefficient × Velocity
In UI animation terms this force is often represented as a number between 0 and 1.
And depending on the value, they even get some fancy names.
- Critically damped Damping = 1 (The most common in Apple UI)
- Over Damped Damping > 1 (Not a good look, not often use in UI animation)
- Under Damped Damping < 1 (The smaller the number the bouncier it gets and the more likes you get on Dribbble.)
Now you just add this dampening force (which will be a negative value) to your spring force, and your spring will stop.
With the new damped force, our acceleration formula looks like this.
a = (-kx) + (-dv) / m
And so you get a spring that looks more like this:
Notice that time is not a variable that we’re setting, and normally when you’re animating something you want to tell it how long it’s going to animate for.
Well, I’m sorry to tell you that springs don’t really care about time. They’ll just take however long it takes them to come to a stop. And that’s a problem for me because I find it more intuitive and feel more in control when I can specify how long my animations will last.
Luckily, math is on our side. Your mass, stiffness, and damping all influence the time it’ll take for the spring to rest.
That means you can technically control the mass / stiffness / damping to match the exact time you want.
The damping coefficient determines how springy your animation is, so you don’t want to mess with that. So you can either change mass or stiffness to control the final time.
So how do we calculate the mass or stiffness to match our time?
To make things simple, you can set one of them to a constant, so I’ll pick stiffness and set it to 5. I want our damping coefficient to be 0.9, and our animation to last 1 second.
That leaves us with one value to calculate, our mass.
But first, in order to reverse engineer the mass value, we need to know how to calculate the duration for the spring animation to come to a rest - called relaxation time.
The formula for relaxation time is
T = 2/𝜻
Relaxation Time = 2 / Damping Ratio
But not so easy, damping ratio is not the same as the damping coefficient we’ve been setting.
The formula for damping ratio is:
𝜻 = damping coefficient / (2 × square root of(mass × stiffness))
Ok, so now we can calculate time, through some complicated math (or Wolfram Alpha), we can calculate that the formula for mass given a relaxation time of 1 second.
Mass =((time)² × damping²) / (16 × stiffness)
So our final values are:
Stiffness = 5
Damping Coefficient = 0.9
Mass = 0.162
And that spring will rest is approximately 1 second. DONE!
I’ve been doing all this in swift and made a little git gist with the formulas.
Note that this is not the actual code or formula used by Apple for UIViewPropertyAnimator(duration: TimeInterval, dampingRatio: CGFloat) method. They do some complicated secret sauce and calculate not only the mass and stiffness but also recalculate the damping value you just set.
So if you apply the formulas above and make an animator and play alongside an UIViewPropertyAnimation you’ll see slightly different results.