An elastic bounce one liner


Creating a custom easing function

You know that feeling when you try to achieve some simple task for a personal side-project, but what you find online requires you to ship an entire library?  So do I.

Peeke Kuepers

I set out on a quest to find this really specific item I needed for a side-project of mine: a nice easing function with an elastic bounce effect. After a trip through countless stack overflow questions and github pages I struck gold: I found a preset in a physics library, animating my object precisely the way I wanted it to.

However the library came with a whole bunch of overhead. It kept track of a set of state variables which where mutated over time, making it less flexible to use. It was wrapped in a layer of timers, DOM mutation functions and other kinds of fluff. Most of all: it's filesize was hard to justify, since I was only using it for a small animation effect.

So I set out to create a similar effect myself. I eventually managed to substitute the whole library with an one line easing function based on a cosine wave:

const ease = t => t + (1 - t) * (1 - Math.cos(t * frequency * 2 * Math.PI)) 

I started with a plain cosine wave. A cosine wave function provides a constant wave with a nice gradient. The motion looks pretty similar to the spring effect we are trying to mimic. The skeleton of the cosine wave function looks like this:

amplitude * Math.cos(phase + t * freq * 2 * Math.PI)

You can play with these values in the interactive graph below:

 Because we are trying to create an easing function, we have to fulfill two requirements:

  • our output at 0 needs to equal 0
  • our output an 1 needs to equal 1

In between, anything goes.

Because we start at 0, moving up until we reach 1, we want our wave to start in a valley instead of a peak. To do this, we first flip the wave vertically by multiplying by -1. In the resulting wave the valley is located at t = -1 instead of 0, so we add 1 to the result.

// Cosine wave starting with a valley at 0

-1 * Math.cos(t * 2 * Math.PI) + 1

// Let's rewrite that

1 - Math.cos(t * 2 * Math.PI)

// Alternatively, we could shift the phase by Math.PI

Math.cos(Math.PI + t * 2 * Math.PI)

Since we are trying to mimic a spring like motion, we need our wave to have a large amplitude near 0, but gradually decrease to an amplitude of 0 at the end. To do this we describe the amplitude of our wave with a line running from 1 to 0: 1 - t.

(1 - t) * (1 - Math.cos(t * frequency * 2 * Math.PI))

That looks just like the motion we are looking for! 

There's just one issue with it: we do not fulfill the second requirement of an easing function. The function should end at a value of 1, but we end with 0. We have to add 1 to the final result, while making sure the beginning of the wave stays fixed at 0. To do this, we draw a line running from 0 to 1 and add it up to our formula. The ascending line is simply described by t.

t + (1 - t) * (1 - Math.cos(t * frequency * 2 * Math.PI))

And there you have it: 

const ease = t => t + (1 - t) * (1 - Math.cos(t * frequency * 2 * Math.PI))  

A function consisting of only one line, describing without state what I would otherwise have been included as a big library. Sometimes it pays of to take a step back and decide to ship your own solution instead of  just including that other piece of code.

Liked this article?

Why not follow me on Twitter? @peeke__.