Skip to content

Instantly share code, notes, and snippets.

@ShopifyEng
Created April 6, 2020 17:11
Show Gist options
  • 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
@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