Animated Gradient

Monday, October 14, 2024
Are you a mobile app developer looking to add a simple yet powerful linear gradient animation to your React Native project? Look no further. In this tutorial, we'll guide you through the process of creating a beautiful linear gradient animation using React Native, Reanimated, and Skia. This animation might seem trivial, but it's a great example of how using the right tools can greatly simplify your work.
If you're a fan of React Native animations, consider supporting me on Patreon, where I post the source code of a new animation every week.
Feel free to check directly the source code:

YouTube Channel

Are you more of a visual learner? Check out the video version of this tutorial on my YouTube channel:

Project Overview

For this tutorial, I've already set up a React Native project using Expo. I've defined a few things, including a TouchableOpacity component, which logs "pressed" when tapped. The styling for this component is straightforward, using absolute positioning with a custom icon from Expo Vector Icons.
return (
<>
<StatusBar style="light" />
<View style={{ flex: 1 }} />
<TouchableOpacity
style={styles.buttonContainer}
onPress={() => {
console.log('pressed');
}}
>
<FontAwesome name="random" size={24} color="white" />
</TouchableOpacity>
</>
);
To make this animation work, we'll use the Reanimated and React Native Skia packages. Make sure you have a version of Reanimated above 3.0, as the interaction between shared values from Reanimated and Skia components relies on this version. Don't forget to add the Reanimated plugin to your Babel configuration to avoid crashes.

Adding the Linear Gradient

Let's start by defining our linear gradient. The linear gradient is a component imported from React Native Skia, and it should be placed inside a Skia Canvas. The Canvas will take all available space, so we can start by defining it with a flex: 1 style.
import { Canvas, LinearGradient, Rect, vec } from '@shopify/react-native-skia';
// ...
return (
<>
<StatusBar style="light" />
<Canvas style={{ flex: 1 }}>
<Rect x={0} y={0} width={200} height={200}>
<LinearGradient
start={vec(0, 0)}
end={vec(200, 200)}
colors={['red', 'blue']}
/>
</Rect>
</Canvas>
...
</>
);
At this point, the Canvas is taking up the space, but the Rect is not. To set the Rect dimensions, we need to retrieve the width and height of the screen using the useWindowDimensions hook from React Native.
We also need to assign the screen width and height to the width and height of the LinearGradient.
import { useWindowDimensions } from 'react-native';
return (
<>
<StatusBar style="light" />
<Canvas style={{ flex: 1 }}>
<Rect x={0} y={0} width={width} height={height}>
<LinearGradient
start={vec(0, 0)}
end={vec(width, height)}
colors={['red', 'blue']}
/>
</Rect>
</Canvas>
...
</>
);
Here's the result:

Animating the Gradient Colors

This isn't enough to create the gradient animation. To do that, we'll use Reanimated. We'll define two shared values, leftColor and rightColor, which will represent the colors on the left and right sides of the gradient.
import { useSharedValue, withTiming } from 'react-native-reanimated';
// ...
const leftColor = useSharedValue('red');
const rightColor = useSharedValue('blue');
To animate these colors, we can create a derived value, colors, which will be an array containing the values of leftColor and rightColor. This is where the magic happens.
const colors = useDerivedValue(() => {
return [leftColor.value, rightColor.value];
}, []);
Now that we have the colors derived value, we can apply it to the LinearGradient within the Rect. The colors of the gradient will change based on the values of leftColor and rightColor.
<Canvas style={{ flex: 1 }}>
<Rect x={0} y={0} width={width} height={height}>
<LinearGradient
start={vec(0, 0)}
end={vec(width, height)}
colors={colors}
/>
</Rect>
</Canvas>

Updating the colors

We want to animate the gradient colors, so we need to update the leftColor and rightColor values to some random colors.
First off, we need to create a function that generates random colors.
const getRandomColor = () => {
// Generate random RGB color values
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
// Return the color in the format 'rgba(r, g, b, 1)'
return `rgba(${r}, ${g}, ${b}, 1)`;
};
Once we have the function, we can use a TouchableOpacity and update the leftColor and rightColor values whenever it's pressed.
<TouchableOpacity
style={styles.buttonContainer}
onPress={() => {
leftColor.value = getRandomColor();
rightColor.value = getRandomColor();
}}
>
<FontAwesome name="random" size={24} color="white" />
</TouchableOpacity>

Where's the magic?

As you might have noticed, the animation looks snappy, doesn't it? We can easily fix this by using the withTiming function from Reanimated.
leftColor.value = withTiming(getRandomColor());
rightColor.value = withTiming(getRandomColor());

Full Recap

Did you miss something? Here's the full recap of the code we've written so far:
import {
TouchableOpacity,
StyleSheet,
View,
useWindowDimensions,
} from 'react-native';
import { StatusBar } from 'expo-status-bar';
import { FontAwesome } from '@expo/vector-icons';
import { Canvas, LinearGradient, Rect, vec } from '@shopify/react-native-skia';
import {
useDerivedValue,
useSharedValue,
withTiming,
} from 'react-native-reanimated';
// That was in the utils file (but let's keep it here for the sake of simplicity)
const getRandomColor = () => {
// Generate random RGB color values
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
// Return the color in the format 'rgba(r, g, b, 1)'
return `rgba(${r}, ${g}, ${b}, 1)`;
};
const App = () => {
const { width, height } = useWindowDimensions();
const leftColor = useSharedValue('red');
const rightColor = useSharedValue('blue');
const colors = useDerivedValue(() => {
return [leftColor.value, rightColor.value];
}, []);
return (
<>
<StatusBar style="light" />
<Canvas style={{ flex: 1 }}>
<Rect x={0} y={0} width={width} height={height}>
<LinearGradient
start={vec(0, 0)}
end={vec(width, height)}
colors={colors}
/>
</Rect>
</Canvas>
<TouchableOpacity
style={styles.buttonContainer}
onPress={() => {
// Where the magic happens 🪄
leftColor.value = withTiming(getRandomColor());
rightColor.value = withTiming(getRandomColor());
}}
>
<FontAwesome name="random" size={24} color="white" />
</TouchableOpacity>
</>
);
};
// Boring styles
const styles = StyleSheet.create({
buttonContainer: {
position: 'absolute',
bottom: 52,
right: 32,
height: 64,
aspectRatio: 1,
borderRadius: 40,
backgroundColor: '#111',
zIndex: 10,
justifyContent: 'center',
alignItems: 'center',
},
});
export { App };

Conclusion

That's it! You've successfully created a beautiful linear gradient animation using React Native, Reanimated, and Skia. This animation can be a valuable addition to a wide range of use cases in your mobile app projects. If you have any suggestions for future video tutorials, feel free to share them in the comments section. Thank you for joining me in this tutorial, and I look forward to seeing you in the next one.

Join my weekly newsletter

Every week I send out a newsletter sharing new things about React Native animations.