Created
April 6, 2020 17:11
-
-
Save ShopifyEng/96b5f2f55b274dab3a957bb12c3f2c4d to your computer and use it in GitHub Desktop.
Building Arrive's Confetti in React Native with Reanimated - Confetti Final
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
Wow awesome, did anyone by any chance do the porting to the new version of reanimated 3?