How I Transitioned from Ease to Spring Animations
Tags: react, animations, webdev
Published: 2020-03-03 06:45:59

# Lessons learned while building react-spring visualizer.

At [work](https://kaliber.net) we have been transitioning towards [react-spring](https://www.react-spring.io/) and it has quickly become my favorite UI-animation library when working with React; I love its small footprint, modern hook-based API and performant animations without re-renders.

One strong point of react-spring is that it supports physics-based animations, especially those based on springs. These types of animation feel very natural. However, coming from using eases and durations in CSS and libraries such as GSAP, there was __a lot__ that I didn't get at first.

React-spring's configuration accepts properties such as __tension__, __mass__, __friction__ and __velocity__. While I sort of understood the meaning of these words, I had no idea what they meant in the context of a spring animation. Which values would I need to give them to hit that sweet spot, where the animation feels just right?

So I built a tool to help me visualize how these values impact an animation: [react-spring visualizer](https://react-spring-visualizer.com/). In the process of building this tool, I tried to understand how spring based animations work. Below are the things I've learned along the way.

![UI of React-spring visualizer](https://dev-to-uploads.s3.amazonaws.com/i/s53946bcbyn6mx0467yo.png)

By the way, the same things apply to any spring animation, not just react-spring. They are just as relevant if you use [React-Motion](https://github.com/chenglou/react-motion), [Framer motion](https://www.framer.com/motion/) or [Anime.js](https://animejs.com/).

## First, a Refresher on Easing

When you think of an object, for example a car, that goes from A to B, it will not immediately reach full speed and it won't stop immediately when it reaches its destination. Instead it will slowly increase speed, reach its top-speed and slow down before eventually coming to a halt. It will ease in and ease out.

The same goes for UI elements, compare the two animations below:
{% codesandbox linear-vs-ease-in-out-v6ncs %}<figcaption>Tap to play again.</figcaption>


The top one, which moves at a constant speed, feels mechanical and a bit awkward. The bottom one eases in when starting, reaches full speed and eases out before arriving at its destination. Since this mimics the movement of the car above, it feels more natural.

CSS, GSAP, anime.js, even jQuery come with some preset eases, so it's safe to say most frontend developers are familiar with them. A good list of commonly used eases can be found in this [Easing Functions Cheat Sheet](https://easings.net/en).

## So How do Ease-based Animations Work?

An animation with easing always needs at least 2 things: a __duration__ usually expressed in seconds or milliseconds and an __easing function__ representing a _curve_.

Here's an example of an easing curve named quadIn:

```js
function quadIn(t) {
 return t * t
}
```

It's a quadratic function, that's why it's called _quadIn_. If you plot the curve of this function it would look like this:

![A graphic representation of a quadIn easing function](https://dev-to-uploads.s3.amazonaws.com/i/qma81hl7mow04qkdqmhd.png)
<figcaption>A graphic representation of a quadIn easing function.</figcaption>

The start of the progress of the animation is 0 and the end is 1. If you would move a variable called x from 0 to 400 pixels in 750 milliseconds, you would calculate the current position for each frame by interpolating between the values like this:

```js
function update(delta) {
  const t = (delta / 750)
  return quadIn(t) * 400
}
```

_Delta_ is the time since the start of the animation, so after after 200 milliseconds x would be roughly at 28.4 pixels.

```js
const x = (200 / 750) * (200 / 750) * 400 // ≈ 28.4
```

After 700 milliseconds x is at 348.4 pixels, and so on. Below is an example of a quadIn animation.
{% codesandbox ease-quad-in-zmxfu %}<figcaption>Tap to play again.</figcaption>

By the way, above I stated we are _interpolating between values_. An animator might express the same principle by saying they are inbetweening or _tweening_ between _[keyframes](https://en.wikipedia.org/wiki/Key_frame)_. It took me an embarrassingly long time to realize where all these terms originated 🤷‍♂️.

## Why use Spring Animations?

While eases look a lot [nicer](https://dribbble.com/shots/2092963-Event-Website-Design) than linear animations, there's still something unnatural about them; things in the real world rarely move according to a fixed duration and a mathematical function.

For example, what would the easing function of a [falling object](https://giphy.com/gifs/falling-funny-alUHKvX7BJXwc) be? Easing functions are an approximation of how things move in the real world. But we can do better!

In order to mimic how physical objects move, we have to look at… well, physics. And one aspect of physics is especially useful when working with UI animations: the way springs move.

{% codesandbox winter-monad-y6gn8 %}

Spring animations are very useful for UI animations.
* Physics-based animations feel more natural than ease-based animations.
* Spring animations are more intuitive compared to ease-based animations.
* Physics-based animations are interruptible!
* Because spring animations take an initial velocity, they are well adapted to go from a swipe or drag to an animation after release.

It's no wonder they are used extensively in native apps on Android and especially iOS, but for some reason the web trails behind. If you are interested in learning why spring animations work so well, I can highly recommend this [talk by Apple engineers](https://developer.apple.com/videos/play/wwdc2018/803/).

For a comparison, see the two animations below:
{% codesandbox ease-vs-spring-d6p94 %}<figcaption>Tap to play again.</figcaption>

## So How Do Spring Animations Work?

Earlier we saw that for ease-based animations we check every frame: _what's my position based on this time and this function?_

Spring animations work a bit differently: each frame we check: _based on the current position, velocity, mass, tension, and friction what will the position be in the next frame?_

In order to do that we need to know a few properties, like __mass__, __tension__, __friction__ and __velocity__. In react-spring these are defined in the __[config](https://www.react-spring.io/docs/hooks/api#configs)__. It looks like this:

```js
const props = useSpring({
   from: { progress: 0 },
   to: { progress: 400 },
   config: { 
    tension: 170,
    mass: 1,
    friction: 26,
    velocity: 0,
    precision: 0.01
  }
})
```

##So what do these terms mean?

![Parts of a spring animation](https://dev-to-uploads.s3.amazonaws.com/i/l5y6hbfu47znwj83ccsh.png)

Imagine you have a spring hanging from a ceiling with a "bob" on the end. If the spring is at rest, it is at its resting point (or at equilibrium). Pull the bob down.

__Mass__ will be the mass of this bob. While not technically the same, you can think of it as the weight.

The spring itself has a property called __tension__. This is a constant and in the real world is determined by the spring's material, the thickness of the coils, etcetera. Sometimes this is also called __rate__, __spring constant__ or __stiffness__.

__Friction__ is a force acting on the bob reducing the motion of the spring. I prefer to think of it as the density of the surrounding medium, for example air. Air would have lower friction than water or molasses. This is also sometimes referred to as __damping__.

Then there's the __velocity__, which is the initial velocity or speed the spring has when it is released. As if you give the bob a push. Or a pull for a negative velocity.

When the spring reaches its resting point it will bounce around for a while. With each iteration the amplitude will be smaller. __Precision__ is the amplitude at which the animation is stopped.

## The physics

So if our starting point is 0, how do we get to 400 with these config values?

We first calculate the spring force (Fs), by using [Hooke's law](https://en.wikipedia.org/wiki/Hooke's_law).

![Hooke's law](https://dev-to-uploads.s3.amazonaws.com/i/nlka701j61aybe1hpose.png)

Where __k__ is the _tension_ of the spring and __x__ is the _displacement_. The displacement is _the distance between the current length of the spring and the spring length at rest_ (see image above).

```js
const displacement = currentPosition - toPosition
const springForce = -tension * displacement
```

Next, we determine the friction force:

```js
const frictionForce = -friction * currentVelocity
```

Where _currentVelocity_ is the velocity in the current frame, not the initial velocity in the configuration.

From here we can calculate the acceleration, the new velocity and finally the new position, using the [equations of motion](https://en.wikipedia.org/wiki/Equations_of_motion#Constant_linear_acceleration_in_any_direction).

```js
const acceleration = (springForce + frictionForce) / mass
const newVelocity = currentVelocity + acceleration
const newPosition = currentPosition + newVelocity
```

In the next frame the _newVelocity_ and _newPosition_ become the _currentVelocity_ and _currentPosition_.

All together it looks something like this:

```js
let velocity = config.velocity
let position = from.progress
update()
function update() {
  const tensionForce = -tension * (currentPosition - toPosition)
  const dampingForce = -config.friction * velocity
  const acceleration = (tensionForce + dampingForce) / mass
  velocity = velocity + acceleration
  position = position + velocity
  if (Math.abs(position - to.progress) > precision {    
    window.requestAnimationFrame(update)
  }
}
```
Note: this is a very simple example. The folks behind react-spring use a few tricks to make the results more accurate and performant. You can see their [code here](https://github.com/react-spring/react-spring/blob/cd5548a987383b8023efd620f3726a981f9e18ea/src/animated/FrameLoop.ts#L78).

##Building react-spring visualizer

When I set out to build [react-spring visualizer](https://react-spring-visualizer.com/), at first I wanted to use a chart, similar to the excellent [ease visualizer from GSAP](https://greensock.com/ease-visualizer/). But because we are not working with predefined curves and durations, it didn't feel right. So I chose to show the effects of the parameters in a spring.

I have added some visualizers for the most common types of animations: transforms (opacity, translate and rotate) and opacity. Those are the properties I use most of the time when animating DOM elements, since they can be modified by [compositing](https://developers.google.com/web/fundamentals/performance/rendering) alone and don't trigger expensive repaints.

While building this tool, I had the chance to take deep-dive into spring animations, learning way more than I could ever have from reading the documentation (which is a bit sparse) and examples (which are gorgeous, but a bit complex).

I built the app on top of [our](https://kaliber.net/) own build stack: [kaliber/build](https://kaliberjs.github.io/build/) based on webpack and PostCSS with SSR out of the box. It's highly opinionated, but requires zero configuration and it's [open source](https://github.com/kaliberjs/build)!

We use it for all our projects with React, but also for our WordPress based projects. One really cool feature is how it allows you to easily have smaller React applications inside a WordPress site with data from the CMS.

I was allowed to spend some time at work writing this article, as long as I plugged our job vacancies 😁. If you have read this far you're obviously the kind of person we are looking for, so why not join us as [frontend developer](https://werkenbij.kaliber.net/) at Kaliber?

By the way, if you want to know more about how position, velocity and acceleration work together, I can highly recommend checking out [The Nature of Code](https://www.youtube.com/watch?v=uHusbFmq-4I&list=PLRqwX-V7Uu6ZwSmtE13iJBcoI-r4y7iEc&index=4) by Daniel Shiffman. He also has a great [video about springs](https://www.youtube.com/watch?v=cluKQOY92Dw).