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.

Revisions

  1. ShopifyEng created this gist Apr 6, 2020.
    146 changes: 146 additions & 0 deletions Confetti.tsx
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,146 @@
    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