diff --git a/src/index.js b/src/index.js index 77ab760..9182e58 100644 --- a/src/index.js +++ b/src/index.js @@ -1,25 +1,30 @@ -import React from 'react'; -import { View, Text } from 'react-native'; -import MakeItRain from './makeItRain'; -import Confetti from './confetti'; +import React from "react"; +import { View, Text } from "react-native"; +import MakeItRain from "./makeItRain"; +import StraightRain from "./straightRain"; +import Confetti from "./confetti"; function Root(props) { - const itemComponent = props.itemComponent || ; + const itemComponent = props.itemComponent || ( + + ); switch (props.flavor) { case "arrive": - return ( ); + return ; case "rain": - return ( ); + return ; + case "straightrain": + return ; default: - return ( Unsupported flavor ); + return Unsupported flavor; } } Root.defaultProps = { numItems: 100, itemDimensions: { width: 20, height: 10 }, - itemColors: ['#00e4b2', '#09aec5', '#107ed5'], + itemColors: ["#00e4b2", "#09aec5", "#107ed5"], itemTintStrength: 1.0, flavor: "arrive", fallSpeed: 50, diff --git a/src/straightRain.js b/src/straightRain.js new file mode 100644 index 0000000..9b7353c --- /dev/null +++ b/src/straightRain.js @@ -0,0 +1,192 @@ +// Spring physics from https://blog.swmansion.com/simple-physics-with-reanimated-part-3-a168d69faa51 + +import React, { useEffect, useMemo } from "react"; +import Animated from "react-native-reanimated"; +import { View, Dimensions, StyleSheet } from "react-native"; + +const { + View: ReanimatedView, + Clock, + Value, + useCode, + block, + startClock, + stopClock, + set, + abs, + add, + sub, + divide, + diff, + multiply, + cond, + clockRunning, + greaterThan, + lessThan, + lessOrEq, + eq, +} = Animated; + +const randomize = (max, base = 0) => Math.random() * max + base; + +const StraightRain = (props) => { + const [containerDims, setContainerDims] = React.useState( + Dimensions.get("screen") + ); + const [items, setItems] = React.useState(createItems(containerDims)); + const clock = new Clock(); + + useEffect(() => { + return () => { + // func indicates unmount + stopClock(clock); + setItems([]); + }; + }, []); + + // Update item positioning if screen changes (e.g. rotation) + const onLayout = (event) => { + setContainerDims({ + width: event.nativeEvent.layout.width, + height: event.nativeEvent.layout.height, + }); + }; + + function createItems(dimensions) { + return useMemo(() => { + const { width, height } = dimensions; + // Adapt velocity props + const xVelMax = props.horizSpeed; + const yVelMax = props.fallSpeed * 3; + const angleVelMax = props.flipSpeed; + + return [...new Array(props.numItems)].map((_, index) => { + let x = randomize(width - props.itemDimensions.width); + let y = -props.itemDimensions.height * 4; + let anchor = randomize(width / 3, width / 12); + let xArc = Math.abs(x - anchor); + + return { + index, + x: new Value(x), + y: new Value(y), + xArc: new Value(xArc), + yBase: new Value(y), + angle: new Value(0), + anchor: new Value(anchor), + tension: new Value((4 * Math.min(xArc, width / 2)) / width), + xVel: new Value(randomize(xVelMax / 2, xVelMax / 2)), + yVel: new Value(randomize(yVelMax, yVelMax)), + angleVel: new Value(randomize(angleVelMax / 2, angleVelMax / 2)), + delay: new Value(((index / props.numItems) * height) / yVelMax), + color: props.itemColors[index % props.itemColors.length], + }; + }); + }, [dimensions]); + } + + const useDraw = (_items) => { + const nativeCode = useMemo(() => { + const timeDiff = diff(clock); + const nativeCode = _items.map( + ({ x, yBase, angle, xVel, yVel, angleVel, delay }) => { + const dt = divide(timeDiff, 1000); + const dy = multiply(dt, yVel); + const dAngle = multiply(dt, angleVel); + + return [ + cond( + lessOrEq( + yBase, + containerDims.height + props.itemDimensions.height + ), + cond( + greaterThan(delay, 0), + [set(delay, sub(delay, dt))], + [ + set(yBase, add(yBase, dy)), + set(x, add(x, multiply(xVel, dt))), + set(angle, add(angle, dAngle)), + ] + ) + ), + cond( + eq(props.continuous, true), + cond( + greaterThan( + yBase, + containerDims.height + props.itemDimensions.height + ), + set(yBase, -props.itemDimensions.height * 4) + ) + ), + ]; + } + ); + + nativeCode.push(cond(clockRunning(clock), 0, startClock(clock)), clock); + return block(nativeCode); + }, [_items]); + + useCode(() => nativeCode, [nativeCode]); + }; + + useDraw(items); + + return ( + + {items.map(({ index, x, y, angle, color: backgroundColor }) => { + return ( + + {props.itemComponent} + + + ); + })} + + ); +}; + +export default StraightRain; + +const styles = StyleSheet.create({ + animContainer: { + position: "absolute", + top: 0, + left: 0, + alignItems: "center", + justifyContent: "center", + overflow: "hidden", + }, + itemContainer: { + position: "absolute", + top: 0, + left: 0, + }, +});