Home

# 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 that1 - Math.cos(t * 2 * Math.PI) // Alternatively, we could shift the phase by Math.PIMath.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.

Why not follow me on Twitter? @peeke__.