I’m working on a personal project called KaoCards, a flashcard app for remembering people’s names and faces. When I designed the product I thought it’d be cool to have an animated gradient as the background, but when I tried to implement, it turned out to be more work than I thought.
So here’s the story of how I managed to get it working. If you want the TL;DR, I made a repository you can follow along.
A naive attempt.
If you want to understand this, it’s important to know why my first attempt didn’t work.
To display a gradient in React Native, people use a project called react-native-linear-gradient. I wanted to see if anybody had tried animating the colors, and I found the Animated Gradient Transition example on that repo. Looking through the code, I had this reaction:
I didn’t understand why it needed two classes, why there was so much code. I didn’t think you’d need that much. I decided I wasn’t going to bother understanding all of that. I figured the way to get this done should be quite simple:
- Create an
- Interpolate some colors and pass them to
Simple, right? Here’s the source (it will crash). I made an experimental app, ran it, and then, womp womp. We get the error:
JSON value `<null>` of type NSNull cannot be converted to a UIColor. Did you forget to call processColor() on the JS side?
Something somewhere down the line wasn’t getting colors values. So I turned to Twitter for help:
Jason Brown responded with the key insight:
Aaahhh ok! Animated doesn’t work with arrays. Although I thought I was doing everything correctly, the Animated library doesn’t process the values of array props, so the underlying native component is getting garbage instead of getting animated colors.
It became clear why the original example was so big.
Building it correctly.
Ok, understanding this limitation about Animated, let’s modify our game plan and make it a little more robust.
- We want our main component,
AnimatedGradient, to work just like
LinearGradient. It should take an array of colors.
- We want a transition to occur when we change the
colorsprop. To do this,
AnimatedGradientneeds to keep track of the previous colors.
- Since we can’t animate values in arrays, we can build a
GradientHelpercomponent that takes colors individually, and call
GradientHelperwill put the values into an array and pass them to the
LinearGradientcomponent from the
To keep things simple for this example, we’re going to assume that the
colors array only has 2 values.
First, we’ll create an
AnimatedGradientHelper out of our
GradientHelper, which we'll make in a bit.
In the constructor of
AnimatedGradient, we'll initialize a
prevColors state field to keep track of the previous colors. We also initialize an
getDerivedStateFromProps, we take the
state.colors value and stick in
state.prevColors. We set the new
state.colors, and we reset the tweener.
componentWillUpdate (aka when props change), we'll make the tweener move from 0 to 1.
render method, we use the
colors to create two color interpolations, and pass them individually to our
GradientHelper, all we're doing is taking the
color2 props, putting them into an array, and passing that to
LinearGradient. We're doing this because we need to get around Animated's limitations.
And that’s the gist of it. Here’s the demo:
Now we know why the original example is so big. It had to do all this stuff, and handle gradients with more than 2 colors.
But wait, what else can we animate?
We can actually go the extra step and animate other properties. The LinearGradient component lets you specify coordinates for the start and end of the gradient. Why not interpolate those too? Here’s an updated render method. You can probably guess what happened in the rest of the component. Source
We’ll just need to tweak our
GradientHelper a bit by doing something with the props. Source
And now we have a cooler demo.
I was able to combine this animated gradient with some other animations to create a cool background effect for my KaoCards project:
So, now you know how to animate gradients in React Native, and a little bit more about how Animated works. What other properties do you think you can animate?
Thanks to Jason Brown for providing the key insight. Everything I’ve learned about animating stuff in React Native I’ve learned from him. Make sure you follow him on Twitter, and check out his React Native Animations course.