Interpolation with React Native Animations

evening kid
8 min readFeb 2, 2021

When baking cookies, you go from one solid little square to a soft medium piece. Not just one aspect of it changed over time but quite a few including its color.

In React Native, it is important to know how one animation can be mapped to many others at the same time. In today’s chapter, let’s learn about interpolation.

Growing and changing cookies :)

This was first published as a video for the How To Animated series. If you learn better through audio/image, I highly recommend it.

This article is part of a series. If you are unfamiliar with the basics of animations (Animated.Value, Animated.View), the first chapter should help you to understand what’s next.

Interpolation

Before looking at how interpolation works in React Native, we need to learn what it is. Let’s start with an example:

Here’s the situation. We have a function that all we know about is the following: f(0) equals 0 and f(10) equals 20. At this point, can you guess what f(5) might equal to?

Well, from what we see, we could suggest 10, because 5 is between 0 and 10, so the result should be between 0 and 20.

This intuitive process we did is what interpolation does, but let me give you a better definition:

Given examples, “interpolation provides a means of estimating the function at intermediate points” — Wikipedia

If you look back at what we had before, without knowing what the function is, just by looking at the two examples, we could guess what any value ranging between 0 and 10 would be equal to when passed to that function.

In React Native, this concept also applies to other realms such as colors:

Here, we see that the resulting color goes from white to black. Following our intuition, we might think of gray because that’s what we get between these two values. Going with x equals 7.5, we get a dark gray and so on. So you could say we interpolated numbers to colors here.

Another trivial example, the last one I promise, will be with degrees:

Here if x equals 0, we get 0 degrees and if it equals 10, we get 360 degrees. So again, if we pick x equals 5, the result will be between 0 and 360, so 180 degrees.

Now, before we move to the next section, I want to highlight that x could be anything else than 0, 5 and 10. We could be looking at x ranging from 10 to 0, or 5 to 1 million…and the function results could also be going from let’s say 0 to -360.

Whichever values you take as examples, with interpolation you can figure out what are the intermediate values for that same function, without explicitly saying what the function is.

Now that you understand interpolation, let’s see how this can be useful in React Native.

value.interpolate

We’ll take a regular square with a simple animation that moves it 100 pixels to the right.

import React, { useEffect, useRef } from 'react';
import { Animated } from 'react-native';
export default () => {
const translation = useRef(new Animated.Value(0)).current;

useEffect(() => {
Animated.timing(translation, {
toValue: 100,
duration: 1000,
useNativeDriver: true,
}).start();
}, []);

return (
<Animated.View
style={{
width: 100,
height: 100,
backgroundColor: 'orange',
transform: [
{ translateX: translation },
],
}}
/>
);
}

What we want, while this animation is playing, is to also change the opacity and rotate this square.

The problem is that I can’t set the opacity to translation, because opacity goes from 0 to 1, not from 0 to 100:

height: 100,
opacity: translation,

Instead, we could use interpolation to our advantage here. When translation’s value changes from 0 to 100, we want to interpolate the value from 0 to 1:

If you keep the logic we had before, then if x equals 50, f(50) would be 0.5.

This is exactly what we need to animate the opacity while the square is being moved to the right. To interpolate our translation, we can use .interpolate on any animated value:

height: 100,
opacity: translation.interpolate(),

Interpolating needs two things: the x-s, the input range, and f(x)-s, the output range. Let’s write this down:

opacity: translation.interpolate({
inputRange: [0, 100],
outputRange: [0, 1],
}
),

This means that opacity will equal 0.5 when the translation is 50, and 1 when it is 100.

What is great with this helper function is that you can add even more examples to change the interpolation behaviour.

For example, let’s say we want the opacity to start at 0, then 1 at 50 pixels, and finally 0 again when it reaches 100. To do so, we need to add another x, 50, and change our outputs:

opacity: translation.interpolate({
inputRange: [0, 50, 100],
outputRange: [0, 1, 0],
}),

You could also add more inputs to change the interpolation behaviour, it’s all up to you!

Great, now that it worked with numbers, let’s add a rotation which uses degrees instead. Following the same logic, we’ll keep the initial input range that goes from 0 to 100:

transform: [
{ translateX: translation },
{
rotate: translation.interpolate({
inputRange: [0, 100],
})
},

],

But the output range here will be different since we want the rotation to be 0 degrees at 0, and 360 degrees when it reaches 100:

transform: [
{ translateX: translation },
{
rotate: translation.interpolate({
inputRange: [0, 100],
outputRange: ['0deg', '360deg'],
}),
},
],

I know it feels odd to be doing this using strings here but trust me it works just as well as with numbers.

Another interesting example is to interpolate colours. Now, this one is a bit tricky for one reason which is: Animated cannot animate some style properties with native driven animations.

This means that if you animate a backgroundColor property using the native driver, you should get an error saying that some properties cannot be animated this way.

Since backgroundColor won’t be animated using the native driver, let’s keep this in mind when writing the code.

In this case, all you need is to set useNativeDriver to false, which might slow down your animations on low-end devices.

As we did with the rotation, we can animate the square color from orange to blue:

useEffect(() => {
Animated.timing(translation, {
toValue: 100,
duration: 1000,
useNativeDriver: false,
}).start();
}, []);
backgroundColor: translation.interpolate({
inputRange: [0, 100],
outputRange: ['orange', 'blue'],
}),

Alright now, before going to the recap, you need to know about one last thing.

Extrapolation

Let’s say I don’t want to change the opacity when the animation starts, but only when translation reaches 25 pixels:

opacity: translation.interpolate({
inputRange: [25, 50, 100],
outputRange: [0, 1, 0],
}),

Now the problem is that this input range doesn’t cover the whole range of values transition can take which is from 0 to 100:

If you run the following, you might see weird results since React Native by default, will try to extend your range and guess what could be the value when the translation is between 0 and 25.

This is what extrapolation is, “what should happen outside of the ranges and what kind of pattern the outside values should follow”.

To fix this default behaviour, we can cover the whole range again by adding another 0 before 25 when translation equals 0:

This works great but let’s change the rules one last time: imagine that we have no idea what the boundaries are. Forget about this 0 to 100 range and think of a situation where this could go down to -500.

Of course, your code would still need to start turning the opacity up only after reaching 25 pixels.

In this case, the solution would be to clamp your ranges which mean “whatever comes before that range, we’ll keep the last given value”:

So if we clamp our extrapolation on the left side, this means that any value for translation below 25 will stay at 0. Likewise, if we clamp on the right side, any value that goes beyond 100 will also equal to 0.

Code-wise, it’s pretty straightforward:

opacity: translation.interpolate({
inputRange: [25, 50, 100],
outputRange: [0, 1, 0],
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',

}),

As a side-note, the default mode for extrapolation is “extend” which again is React Native guessing what the other values might be. You can override this behaviour with both extrapolate left and right according to your situation.

A shorthand when you want to clamp or extend on both sides is to use the extrapolate property:

opacity: translation.interpolate({
inputRange: [25, 50, 100],
outputRange: [0, 1, 0],
extrapolate: 'clamp',
}),

Pretty simple, isn’t it? Okay now that you know about interpolation and extrapolation, let’s wrap this up.

Recap

If there are three things to remember today, here they are:

  • Interpolation is a way of estimating a function at intermediate points, learning from the ranges you provide.
  • In React Native, you can interpolate animated values using .interpolate which takes an inputRange, that interpolates and maps the values to an outputRange. Great thing is that you can interpolate numbers not just to other numbers but also to colours and degrees.
  • You can change the way it extrapolates your output range by clamping either on the left, right or both sides of the range.

Alright, that’s it for now. In the next chapter, we will look at animated events with scrollviews using what we’ve learned today.

New chapters will be released on a weekly-basis. Remember to follow me if you don’t want to miss any.

--

--

evening kid

I often find myself reading too many articles on the internet