Skip to content

Instantly share code, notes, and snippets.

@ShopifyEng
Created April 6, 2020 17:11
Show Gist options
  • Star 29 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save ShopifyEng/96b5f2f55b274dab3a957bb12c3f2c4d to your computer and use it in GitHub Desktop.
Save ShopifyEng/96b5f2f55b274dab3a957bb12c3f2c4d to your computer and use it in GitHub Desktop.
Building Arrive's Confetti in React Native with Reanimated - Confetti Final
import React, {useMemo} from 'react'
import Animated from 'react-native-reanimated'
import {View, Dimensions, StyleSheet} from 'react-native'
import FastImage from 'react-native-fast-image'
import ConfettiImage from 'assets/images/confetti.png'
const NUM_CONFETTI = 100
const COLORS = ['#00e4b2', '#09aec5', '#107ed5']
const CONFETTI_SIZE = 16
const createConfetti = () => {
const {width: screenWidth} = Dimensions.get('screen')
return [...new Array(NUM_CONFETTI)].map((_, i) => {
const clock = new Animated.Clock()
return {
key: i,
// Spawn confetti from two different sources, a quarter
// from the left and a quarter from the right edge of the screen.
x: new Animated.Value(
screenWidth * (i % 2 ? 0.25 : 0.75) - CONFETTI_SIZE / 2
),
y: new Animated.Value(-60),
angle: new Animated.Value(0),
xVel: new Animated.Value(Math.random() * 400 - 200),
yVel: new Animated.Value(Math.random() * 150 + 150),
angleVel: new Animated.Value((Math.random() * 3 - 1.5) * Math.PI),
delay: new Animated.Value(Math.floor(i / 10) * 0.3),
elasticity: Math.random() * 0.3 + 0.1,
color: COLORS[i % COLORS.length],
clock,
}
})
}
const Confetti = () => {
const confetti = useMemo(createConfetti, [])
return (
<View pointerEvents="none" style={StyleSheet.absoluteFill}>
{confetti.map(
({
key,
x,
y,
angle,
xVel,
yVel,
angleVel,
color,
elasticity,
delay,
clock,
}) => {
return (
<React.Fragment key={key}>
<Animated.Code>
{() => {
const {
startClock,
set,
add,
sub,
divide,
diff,
multiply,
cond,
clockRunning,
greaterThan,
lessThan,
} = Animated
const {width: screenWidth} = Dimensions.get('window')
const timeDiff = diff(clock)
const dt = divide(timeDiff, 1000)
const dy = multiply(dt, yVel)
const dx = multiply(dt, xVel)
const dAngle = multiply(dt, angleVel)
return cond(
clockRunning(clock),
[
cond(
greaterThan(delay, 0),
[set(delay, sub(delay, dt))],
[
set(y, add(y, dy)),
set(x, add(x, dx)),
set(angle, add(angle, dAngle)),
]
),
cond(greaterThan(x, screenWidth - CONFETTI_SIZE), [
set(x, screenWidth - CONFETTI_SIZE),
set(xVel, multiply(xVel, -elasticity)),
]),
cond(lessThan(x, 0), [
set(x, 0),
set(xVel, multiply(xVel, -elasticity)),
]),
],
[startClock(clock), timeDiff]
)
}}
</Animated.Code>
<Animated.View
style={[
styles.confettiContainer,
{
transform: [
{translateX: x},
{translateY: y},
{rotate: angle},
{rotateX: angle},
{rotateY: angle},
],
},
]}
>
<FastImage
tintColor={color}
source={ConfettiImage}
style={styles.confetti}
/>
</Animated.View>
</React.Fragment>
)
}
)}
</View>
)
}
const styles = StyleSheet.create({
confettiContainer: {
position: 'absolute',
top: 0,
left: 0,
},
confetti: {
width: CONFETTI_SIZE,
height: CONFETTI_SIZE,
},
})
export default Confetti
@peralmq
Copy link

peralmq commented Apr 14, 2020

Thanks for a great guide!

We used this to build a nice confetti based on an SVG logo but ran into some performance problems (similar to software-mansion/react-native-reanimated#381) which we solved by moving the Animated.Code code into the Animated.useCode hook (and make it dependent on the confetti list).

Might make sense for you too? Since you're already have a functional component that is using hooks like the useMemo one.

@peacechen
Copy link

peacechen commented Apr 16, 2020

@peralmq
I also get the "excessive number of pending callbacks" error. Would you mind sharing how you used the useCode hook? The Reanimated documentation is very sparse on that.

Here's the adaptation in an NPM package:
https://www.npmjs.com/package/react-native-make-it-rain

@imjamescrain
Copy link

imjamescrain commented Mar 2, 2022

This was huge! Thanks so much. For those interested, I've ported this gist over to Reanimate v2. You can find it here: https://gist.github.com/imcrainjames/e86893a1d6f85328174d036a9b263dd0

Also a shout to @wcandillon. His excellent content helped me figure this out.

@itsramiel
Copy link

Can you add a link to the Confetti image

@outaTiME
Copy link

Wow awesome, did anyone by any chance do the porting to the new version of reanimated 3?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment