Animating between units with react-spring
Tags: javascript, react, animations, reactspring
Published: 2019-11-21 14:44:50


It's no secret, that on the web we have to deal with different units - from rems and pixels to percentages and viewport-based values. In this tutorial, we'll explore the problem of animating between different units, and see how we can overcome it.

## The problem

Let's start by creating this simple animation where a div with pixel-based size expands to fill the entire viewport width and height when we click on it:

![](https://thepracticaldev.s3.amazonaws.com/i/vu71uqfopaczgawo79jv.gif)


To create this animation, we'll use `useSpring` hook from `react-spring` package, and set the width and the height of the box to 200px when it's not expanded, and to 100vh and 100vw when it is. We'll also remove 10px border-radius when the box is expanded:

{% gist https://gist.github.com/koss-lebedev/9c6c42505d4a41ea69c9acc1da16e988 %}

The result will look like this:

![](https://thepracticaldev.s3.amazonaws.com/i/4oyaj8o2iv2aaru1vh9c.gif)


As we can see, the border-radius animation is working, but the box gets smaller instead. Why is that?

To understand the problem, we need to look at how `react-spring` (and most of React animation libraries for that matter) handles animation between units. When we pass width and height values as strings, `react-spring` will parse the numeric values from the "from" and "to" values, take the unit from the "from" value, and completely ignore the unit of the "to" value:

![](https://thepracticaldev.s3.amazonaws.com/i/is1y9cvjvdl755688tmz.gif)

In our example, the initial state of the box is collapsed and the height of the box is pixel-based, so when `react-spring` starts animating it, it'll use "pixels" as a unit. If instead the initial state was expanded and the height was viewport-based, then the animation would use "vh" as a unit and run from 100vh to 200vh instead.

The border-radius animation works fine because if uses pixels for both expanded and collapsed states.

> Side note: the only library that I found that handles animation between units correctly out-of-the-box is [framer-motion](https://www.framer.com/motion/). I have an [intro level article](https://konstantinlebedev.com/framer-motion-intro/) about framer-motion, and you can also sign up for my upcoming [framer-motion course](https://konstantinlebedev.com/framer-motion/).

## The solution

To fix this problem, we need to make sure that both the initial and the target value use the same unit. We can easily convert viewport-based values into pixels with these simple calculations:

{% gist https://gist.github.com/koss-lebedev/8f5c627047643be618fada06437910c3 %}

Now instead of using viewport-based values, we'll use our helper functions to set the width and the height of the box:

{% gist https://gist.github.com/koss-lebedev/202816bd6fc1ba04dd733dd2df1b8c6a %}

This solves the problem only partially because if we resize the browser window after the animation has run, we'll discover a different issue - the box doesn't adjust to the viewport size anymore since now it has pixel-based size:

![](https://thepracticaldev.s3.amazonaws.com/i/gjme2bpilcme6e47ae78.gif)

We can fix this issue by setting the box size back to viewport-based values once the animation finishes. First of all, we'll use `useRef` hook to hold a reference to the actual DOM node of our box. Secondly, `react-spring` provides a handy `onRest` callback that fires at the end of each animation, so we can use it to check if we animated to the expanded state, and if so, we'll set the box width and height directly.

{% gist https://gist.github.com/koss-lebedev/5ca96fc7d1ff6ec345a91a44c4733171 %}

With this setup, animation works fine - it uses pixel values while animating, and sets the box dimensions to viewport-based size upon completion, so the box remains responsive even if we resize the browser afterward.

![](https://thepracticaldev.s3.amazonaws.com/i/vu71uqfopaczgawo79jv.gif)

You can find working CodeSandbox demo [here](https://codesandbox.io/s/animate-across-units-react-spring-d7q8e).

## Conclusion

Animation libraries such as `react-spring` give us a greater degree of control over our animations compared to CSS animations, but they have  shortcomings as well. Animating values between units is one of them, and it requires us to do extra work to make sure that our animation runs smoothly and remains responsive.