Shopify is investing in React Native as our primary choice of mobile technology moving forward. As a part of this we’ve rewritten our package tracking app Arrive with React Native and launched it on Android—an app that previously only had an iOS version.
One of the most cherished features by the users of the Arrive iOS app is the confetti that rains down on the screen when an order is delivered. The effect was implemented using the built-in CAEmitterLayer class in iOS, producing waves of confetti bursting out with varying speeds and colors from a single point at the top of the screen.
When we on the Arrive team started building the React Native version of the app, we included the same native code that produced the confetti effect through a Native Module wrapper. This would only work on iOS however, so to bring the same effect to Android we had two options before us:
- Write a counterpart to the iOS native code in Android with Java or Kotlin, and embed it as a Native Module.
As you might have guessed from the title of this blog post, we decided to go with the second option. To keep the code as performant as the native implementation, the best option would be to write it in a declarative fashion with the help of the Reanimated library.
I’ll walk you through, step by step, how we implemented the effect in React Native, while also explaining what it means to write an animation declaratively.
When we worked on this implementation, we also decided to make some visual tweaks and improvements to the effect along the way. These changes make the confetti spread out more uniformly on the screen, and makes them behave more like paper by rotating along all three dimensions.
Laying Out the Confetti
To get our feet wet, the first step will be to render a number of confetti on the screen with different colors, positions and rotation angles.
Initialize the view of 100 confetti
We initialize the view of 100 confetti with a couple of randomized values and render them out on the screen. To prepare for animations further down the line, each confetto (singular form of confetti, naturally) is wrapped with Reanimated's
Animated.View. This works just like the regular React Native
View, but accepts declaratively animated style properties as well, which I’ll explain in the next section.
Defining Animations Declaratively
In React Native, you generally have two options for implementing an animation:
requestAnimationFrameon every frame to update the properties of a view.
- Use a declarative API, such as Animated or Reanimated, that allows you to declare instructions that are sent to the native UI-thread to be run on every frame.
Animating the Confetti
We are now ready to apply animations to the confetti that laid out in the previous step. Let's start by updating our
Instead of randomizing x, y and angle, we give all confetti the same initial values but instead randomize the velocities that we're going to be applying to them. This creates the effect of all confetti starting out inside an imaginary confetti cannon and shooting out in different directions and speeds. Each velocity expresses how much a value will be changing for each full second of animation.
We need to wrap each value that we're intending to animate with
Animated.Value, to prepare them for declarative instructions. The
Animated.Clock value is what's going to be the driver of all our animations. As the name implies it gives us access to the animation's time, which we'll use to decide how much to move each value forward on each update.
Further down, next to where we’re mapping over and rendering the confetti, we add our instructions for how the values should be animated:
Before anything else, we set up our
dt (delta time) value that will express how much time has passed since the last update, in seconds. This decides the x, y, and angle delta values that we're going to apply.
To get our animation going we need to start the clock if it's not already running. To do this, we wrap our instructions in a condition,
cond, which checks the clock state and starts it if necessary. We also need to call our
timeDiff (time difference) value once to set it up for future use, since the underlying
diff function returns its value’s difference since the last frame it evaluated, and the first call will be used as the starting reference point.
The declarative instructions above roughly translate to the following pseudo code, which runs on every frame of the animation:
Considering the nature of confetti falling through the air, moving at constant speed makes sense here. If we were to simulate more solid objects that aren't slowed down by air resistance as much, we might want to add a
yAcc (y-axis acceleration) variable that would also increase the
yVel (y-axis velocity) within each frame.
Everything put together, this is what we have now:
The confetti is starting to look like the original version, but our React Native version is blurting out all the confetti at once, instead of shooting them out in waves. Let's address this by staggering our animations:
We add a delay property to our confetti, with increasing values for each group of 10 confetti. To wait for the given time delay, we update our animation code block to first subtract
dt from delay until it reaches below 0, after which our previously written animation code kicks in.
Now we have something that pretty much looks like the original version. But isn’t it a bit sad that a big part of our confetti is shooting off the horizontal edges of the screen without having a chance to travel across the whole vertical screen estate? It seems like a missed potential.
Containing the Confetti
Instead of letting our confetti escape the screen on the horizontal edges, let’s have them bounce back into action when that’s about to happen. To prevent this from making the confetti look like pieces of rubber macaroni bouncing back and forth, we need to use a good elasticity multiplier to determine how much of the initial velocity to keep after the collision.
When an x value is about to go outside the bounds of the screen, we reset it to the edge’s position and reverse the direction of xVel while reducing it by the elasticity multiplier at the same time:
Adding a Cannon and a Dimension
We’re starting to feel done with our confetti, but let’s have a last bit of fun with it before shipping it off. What’s more fun than a confetti cannon shooting 2-dimensional confetti? The answer is obvious of course—it’s two confetti cannons shooting 3-dimensional confetti!
We should also consider cleaning up by deleting the confetti images and stopping the animation once we reach the bottom of the screen, but that’s not nearly as fun as the two additions above so we’ll leave that out of this blog post.
This is the result of adding the two effects above:
The final full code for this component is available in this gist.