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.

https://twitter.com/peeke__/status/901811099472977924 (Re)published onI 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.