Skip to content

Instantly share code, notes, and snippets.

@chrise86
Forked from ShopifyEng/Confetti.tsx
Created April 9, 2020 07:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chrise86/379446137ed7b2098d3f7bb4a6ca0da5 to your computer and use it in GitHub Desktop.
Save chrise86/379446137ed7b2098d3f7bb4a6ca0da5 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment