├── .gitignore ├── Example ├── .gitignore ├── App.js ├── app.json ├── assets │ ├── cashMoney.js │ └── icon.png ├── babel.config.js └── package.json ├── LICENSE.MD ├── README.MD ├── package.json └── src ├── confetti.js ├── index.js └── makeItRain.js /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | jsconfig.json 3 | .vscode 4 | 5 | # OSX 6 | # 7 | .DS_Store 8 | 9 | # Xcode 10 | # 11 | build/ 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata 21 | *.xccheckout 22 | *.moved-aside 23 | DerivedData 24 | *.hmap 25 | *.ipa 26 | 27 | # Android/IJ 28 | # 29 | .idea 30 | .gradle 31 | local.properties 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | Example/react-native-make-it-rain/ 38 | package-lock.json 39 | yarn.lock 40 | 41 | # BUCK 42 | buck-out/ 43 | \.buckd/ 44 | android/app/libs 45 | android/keystores/debug.keystore 46 | -------------------------------------------------------------------------------- /Example/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p8 6 | *.p12 7 | *.key 8 | *.mobileprovision 9 | *.orig.* 10 | web-build/ 11 | web-report/ 12 | 13 | # macOS 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /Example/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { StyleSheet, Text, View, Switch } from 'react-native'; 3 | import MakeItRain from './react-native-make-it-rain'; 4 | import CashMoney from './assets/cashMoney'; 5 | 6 | export default function App() { 7 | const [isConfetti, setIsConfetti] = useState(true); 8 | const toggleSwitch = () => setIsConfetti(previousState => !previousState); 9 | 10 | return ( 11 | 12 | Make It Rain 13 | { isConfetti ? 14 | 🤍} 17 | itemTintStrength={0.8} 18 | /> 19 | : 20 | } 24 | itemColors={['#ffffff00']} 25 | flavor={"rain"} 26 | /> 27 | } 28 | 29 | 30 | rain 31 | 39 | arrive 40 | 41 | 42 | ); 43 | } 44 | 45 | const styles = StyleSheet.create({ 46 | container: { 47 | flex: 1, 48 | backgroundColor: '#fff', 49 | alignItems: 'center', 50 | justifyContent: 'space-around', 51 | }, 52 | switchRow: { 53 | flexDirection: 'row', 54 | paddingVertical: 30, 55 | alignItems: 'center', 56 | justifyContent: 'center', 57 | }, 58 | font: { 59 | fontSize: 40, 60 | }, 61 | switch: { 62 | marginHorizontal: 20, 63 | transform: [{ scaleX: 2 }, { scaleY: 2 }] 64 | } 65 | }); 66 | -------------------------------------------------------------------------------- /Example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "Make It Rain Example", 4 | "slug": "Example", 5 | "platforms": [ 6 | "ios", 7 | "android", 8 | "web" 9 | ], 10 | "version": "1.0.0", 11 | "orientation": "portrait", 12 | "icon": "./assets/icon.png", 13 | "splash": { 14 | "image": "./assets/icon.png", 15 | "resizeMode": "contain", 16 | "backgroundColor": "#ffffff" 17 | }, 18 | "updates": { 19 | "fallbackToCacheTimeout": 0 20 | }, 21 | "assetBundlePatterns": [ 22 | "**/*" 23 | ], 24 | "ios": { 25 | "supportsTablet": true 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Example/assets/cashMoney.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { 4 | Svg, 5 | Circle, 6 | Ellipse, 7 | G, 8 | LinearGradient, 9 | RadialGradient, 10 | Line, 11 | Path, 12 | Polygon, 13 | Polyline, 14 | Rect, 15 | Symbol, 16 | Text, 17 | Use, 18 | Defs, 19 | Stop 20 | } from 'react-native-svg'; 21 | 22 | // Note: This SVG is offset to the right which prevents the tint overlay from aligning properly. 23 | export default function CashMoney(props) { 24 | return ( 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /Example/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peacechen/react-native-make-it-rain/0ec65022db0492c504726d8f1b1661360baa7cdf/Example/assets/icon.png -------------------------------------------------------------------------------- /Example/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /Example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "scripts": { 4 | "start": "expo start", 5 | "cp": "cpx ../src/** react-native-make-it-rain -u", 6 | "postinstall": "npm run cp", 7 | "android": "expo start --android", 8 | "ios": "expo start --ios", 9 | "web": "expo start --web", 10 | "eject": "expo eject" 11 | }, 12 | "dependencies": { 13 | "expo": "~37.0.3", 14 | "react": "~16.9.0", 15 | "react-dom": "~16.9.0", 16 | "react-native": "https://github.com/expo/react-native/archive/sdk-37.0.1.tar.gz", 17 | "react-native-reanimated": "^1.8.0", 18 | "react-native-svg": "11.0.1", 19 | "react-native-web": "~0.11.7" 20 | }, 21 | "devDependencies": { 22 | "@babel/core": "^7.8.6", 23 | "babel-preset-expo": "~8.1.0", 24 | "cpx": "^1.5.0" 25 | }, 26 | "private": true 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE.MD: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) Copyright (c) 2020 Peace Chen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # React Native "Make It Rain" 2 | 3 |
4 | 5 | npm package version 6 | 7 | 8 | npm downloads 9 | 10 | 11 | project license 12 | 13 | 14 | make a pull request 15 | 16 |
17 |
18 |
19 | 20 | Tweet 21 | 22 |
23 | 24 | ### v1.0.0 breaking changes 25 | [Prop names](#props) have been generalized to represent that any type of component can be passed in as the falling pieces. 26 | The `itemComponent` (previously `moneyComponent`) no longer defaults to an SVG. This allowed for removal of the dependency on `react-native-svg`. Refer to the Example project for how to pass in an SVG `itemComponent`. 27 | 28 | 29 | ## Summary 30 | 31 | This is a self-contained component that may be placed in any component. It takes up the entire parent view and is positioned absolutely, overlaying other content in the parent. 32 | 33 | The default animation was adapted from [Shopify's Arrive confetti example](https://engineering.shopify.com/blogs/engineering/building-arrives-confetti-in-react-native-with-reanimated). 34 | 35 | 36 | 37 | The original *Make It Rain* animation was adapted from Joel Arvidsson's [MakeItRain example](https://github.com/oblador/react-native-animatable/tree/master/Examples/MakeItRain). It has been rewritten to use `react-native-reanimated`. 38 | 39 | 40 | 41 | The `flavor` [prop](#props) selects between them. 42 | 43 | 44 | ## Sample Code 45 | ```jsx 46 | import React, {Component} from 'react'; 47 | import { View, Text } from 'react-native'; 48 | import MakeItRain from 'react-native-make-it-rain'; 49 | 50 | class Demo extends Component { 51 | render() { 52 | return ( 53 | 54 | Make It Rain 55 | 🤍} 59 | itemTintStrength={0.8} 60 | /> 61 | 62 | ); 63 | } 64 | } 65 | ``` 66 | 67 | ## Usage 68 | 69 | > npm install react-native-make-it-rain --save 70 | 71 | ## Props 72 | | Prop | Description | Type | Default | 73 | | ------------------------------ | ---------------------------------------------------- | -------- | ---------- | 74 | | **`numItems`** | How many items fall | Integer | 100 | 75 | | **`itemComponent`** | Replaces the built-in item component. Can use any React component (Icon, Image, View, SVG etc) | Component | `` | 76 | | **`itemDimensions`** | Size of item. If `itemComponent` is supplied, size should be set to match. | Object | `{ width: 20, height: 10 }` | 77 | | **`itemColors`** | Colors of falling items. | Array | ['#00e4b2', '#09aec5', '#107ed5'] | 78 | | **`itemTintStrength`** | Opacity of color overlay on item (0 - 1.0) | Number | 1.0 | 79 | | **`flavor`** | The animation behavior ("arrive" or "rain") | String | "arrive" | 80 | | **`fallSpeed`** | How fast the item falls | Integer | 50 | 81 | | **`flipSpeed`** | Item flip speed | Integer | 3 | 82 | | **`horizSpeed`** | How fast the item moves horizontally | Integer | 50 | 83 | | **`continuous`** | Rain down continuously | Boolean | true | 84 | 85 | 86 | # Sample Application 87 | 88 | If you'd like to see it in action, run these commands: 89 | ```sh 90 | cd Example 91 | npm run cp 92 | npm install 93 | npm start 94 | ``` 95 | 96 | The sample app is an Expo project created with `create-react-native-app`. 97 | 98 | ## Development 99 | 100 | The source files are copied from the project root directory into `Example/react-native-make-it-rain` using `npm run cp`. If a source file is modified, it must be copied over again with `npm run cp`. 101 | 102 | Ideally the Example project could include the parent's source files using package.json, but that causes a babel interopRequireDefault runtime error. 103 | 104 | 105 | ## Credits 106 | Cash SVG in the Example project was sourced from [IconFinder](https://www.iconfinder.com/icons/1889190/currency_currency_exchange_dollar_euro_exchange_finance_money_icon) and is used under the Creative Commons license. 107 | 108 | ## ToDo 109 | * Tests 110 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-make-it-rain", 3 | "version": "1.1.1", 4 | "main": "src/index.js", 5 | "author": "Peace Chen", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "node node_modules/react-native/local-cli/cli.js start" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/peacechen/react-native-make-it-rain.git" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/peacechen/react-native-make-it-rain/issues" 16 | }, 17 | "homepage": "https://github.com/peacechen/react-native-make-it-rain#readme", 18 | "keywords": [ 19 | "react native", 20 | "react native make it rain", 21 | "react-native-make-it-rain", 22 | "Make It Rain", 23 | "confetti", 24 | "rain", 25 | "money" 26 | ], 27 | "devDependencies": { 28 | "react": ">=16.8.0", 29 | "react-native": ">=0.60.0" 30 | }, 31 | "dependencies": { 32 | "react-native-reanimated": "^1.8.0" 33 | }, 34 | "peerDependencies": {} 35 | } 36 | -------------------------------------------------------------------------------- /src/confetti.js: -------------------------------------------------------------------------------- 1 | // Inpsired by 2 | // https://engineering.shopify.com/blogs/engineering/building-arrives-confetti-in-react-native-with-reanimated 3 | // https://dev.to/hrastnik/implementing-gravity-and-collision-detection-in-react-native-2hk5 4 | 5 | import React, { useEffect, useMemo } from 'react' 6 | import Animated from 'react-native-reanimated' 7 | import {View, Dimensions, StyleSheet} from 'react-native' 8 | 9 | const { 10 | View: ReanimatedView, 11 | Clock, 12 | Value, 13 | useCode, 14 | block, 15 | startClock, 16 | stopClock, 17 | set, 18 | add, 19 | sub, 20 | divide, 21 | diff, 22 | multiply, 23 | cond, 24 | clockRunning, 25 | greaterThan, 26 | lessThan, 27 | lessOrEq, 28 | eq, 29 | } = Animated; 30 | 31 | const Confetti = props => { 32 | const [containerDims, setContainerDims] = React.useState(Dimensions.get('screen')); 33 | const [confetti, setConfetti] = React.useState(createConfetti(containerDims)); 34 | const clock = new Clock(); 35 | 36 | useEffect(() => { 37 | return () => { // func indicates unmount 38 | stopClock(clock); 39 | setConfetti([]); 40 | } 41 | }, []); 42 | 43 | // Update confetti positioning if screen changes (e.g. rotation) 44 | const onLayout = (event => { 45 | setContainerDims({ 46 | width: event.nativeEvent.layout.width, 47 | height: event.nativeEvent.layout.height, 48 | }); 49 | }); 50 | 51 | function createConfetti(dimensions) { 52 | return useMemo(() => { 53 | const { width, height } = dimensions; 54 | // Adapt velocity props 55 | const xVelMax = props.horizSpeed * 8; 56 | const yVelMax = props.fallSpeed * 3; 57 | const angleVelMax = props.flipSpeed; 58 | 59 | return [...new Array(props.numItems)].map((_, index) => { 60 | return { 61 | index, 62 | // Spawn confetti from two different sources, a quarter 63 | // from the left and a quarter from the right edge of the screen. 64 | x: new Value( 65 | width * (index % 2 ? 0.25 : 0.75) - props.itemDimensions.width / 2 66 | ), 67 | y: new Value(-props.itemDimensions.height * 2), 68 | angle: new Value(0), 69 | xVel: new Value(Math.random() * xVelMax - (xVelMax / 2)), 70 | yVel: new Value(Math.random() * yVelMax + yVelMax), 71 | angleVel: new Value((Math.random() * angleVelMax - (angleVelMax / 2)) * Math.PI), 72 | delay: new Value(Math.floor(index / 10) * 0.3), 73 | elasticity: Math.random() * 0.9 + 0.1, 74 | color: props.itemColors[index % props.itemColors.length], 75 | } 76 | }) 77 | return confetti; 78 | }, [dimensions]); 79 | } 80 | 81 | const useDraw = _confetti => { 82 | const nativeCode = useMemo(() => { 83 | const timeDiff = diff(clock); 84 | const nativeCode = _confetti.map(({ 85 | x, 86 | y, 87 | angle, 88 | xVel, 89 | yVel, 90 | angleVel, 91 | color, 92 | elasticity, 93 | delay, 94 | }) => { 95 | const dt = divide(timeDiff, 1000) 96 | const dy = multiply(dt, yVel) 97 | const dx = multiply(dt, xVel) 98 | const dAngle = multiply(dt, angleVel) 99 | 100 | return [ 101 | cond( 102 | lessOrEq(y, containerDims.height + props.itemDimensions.height), 103 | cond( 104 | greaterThan(delay, 0), 105 | [set(delay, sub(delay, dt))], 106 | [ 107 | set(y, add(y, dy)), 108 | set(x, add(x, dx)), 109 | set(angle, add(angle, dAngle)), 110 | ] 111 | ) 112 | ), 113 | cond(greaterThan(x, containerDims.width - props.itemDimensions.width), [ 114 | set(x, containerDims.width - props.itemDimensions.width), 115 | set(xVel, multiply(xVel, -elasticity)), 116 | ]), 117 | cond(lessThan(x, 0), [ 118 | set(x, 0), 119 | set(xVel, multiply(xVel, -elasticity)), 120 | ]), 121 | cond( 122 | eq(props.continuous, true), 123 | cond( 124 | greaterThan(y, containerDims.height + props.itemDimensions.height), 125 | set(y, -props.itemDimensions.height * 2), 126 | ) 127 | ), 128 | ]; 129 | }); 130 | 131 | nativeCode.push(cond(clockRunning(clock), 0, startClock(clock)), clock); 132 | return block(nativeCode); 133 | }, [_confetti]); 134 | 135 | useCode(() => nativeCode, [nativeCode]); 136 | }; 137 | 138 | useDraw(confetti); 139 | 140 | return ( 141 | 142 | {confetti.map( 143 | ({ index, x, y, angle, color: backgroundColor }) => { 144 | return ( 145 | 159 | { props.itemComponent } 160 | 161 | 162 | ) 163 | } 164 | )} 165 | 166 | ) 167 | } 168 | 169 | const styles = StyleSheet.create({ 170 | animContainer: { 171 | position: 'absolute', 172 | top: 0, 173 | left: 0, 174 | alignItems: 'center', 175 | justifyContent: 'center', 176 | overflow: 'hidden', 177 | }, 178 | confettiContainer: { 179 | position: 'absolute', 180 | top: 0, 181 | left: 0, 182 | } 183 | }) 184 | 185 | export default Confetti 186 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, Text } from 'react-native'; 3 | import MakeItRain from './makeItRain'; 4 | import Confetti from './confetti'; 5 | 6 | function Root(props) { 7 | const itemComponent = props.itemComponent || ; 8 | 9 | switch (props.flavor) { 10 | case "arrive": 11 | return ( ); 12 | case "rain": 13 | return ( ); 14 | default: 15 | return ( Unsupported flavor ); 16 | } 17 | } 18 | 19 | Root.defaultProps = { 20 | numItems: 100, 21 | itemDimensions: { width: 20, height: 10 }, 22 | itemColors: ['#00e4b2', '#09aec5', '#107ed5'], 23 | itemTintStrength: 1.0, 24 | flavor: "arrive", 25 | fallSpeed: 50, 26 | flipSpeed: 3, 27 | horizSpeed: 50, 28 | continuous: true, 29 | }; 30 | 31 | export default Root; 32 | -------------------------------------------------------------------------------- /src/makeItRain.js: -------------------------------------------------------------------------------- 1 | // Spring physics from https://blog.swmansion.com/simple-physics-with-reanimated-part-3-a168d69faa51 2 | 3 | import React, { useEffect, useMemo } from 'react' 4 | import Animated from 'react-native-reanimated' 5 | import {View, Dimensions, StyleSheet} from 'react-native' 6 | 7 | const { 8 | View: ReanimatedView, 9 | Clock, 10 | Value, 11 | useCode, 12 | block, 13 | startClock, 14 | stopClock, 15 | set, 16 | abs, 17 | add, 18 | sub, 19 | divide, 20 | diff, 21 | multiply, 22 | cond, 23 | clockRunning, 24 | greaterThan, 25 | lessThan, 26 | lessOrEq, 27 | eq, 28 | } = Animated; 29 | 30 | const randomize = (max, base = 0) => Math.random() * max + base; 31 | 32 | const MakeItRain = props => { 33 | const [containerDims, setContainerDims] = React.useState(Dimensions.get('screen')); 34 | const [items, setItems] = React.useState(createItems(containerDims)); 35 | const clock = new Clock(); 36 | 37 | useEffect(() => { 38 | return () => { // func indicates unmount 39 | stopClock(clock); 40 | setItems([]); 41 | } 42 | }, []); 43 | 44 | // Update item positioning if screen changes (e.g. rotation) 45 | const onLayout = (event => { 46 | setContainerDims({ 47 | width: event.nativeEvent.layout.width, 48 | height: event.nativeEvent.layout.height, 49 | }); 50 | }); 51 | 52 | function createItems(dimensions) { 53 | return useMemo(() => { 54 | const { width, height } = dimensions; 55 | // Adapt velocity props 56 | const xVelMax = props.horizSpeed; 57 | const yVelMax = props.fallSpeed * 3; 58 | const angleVelMax = props.flipSpeed; 59 | 60 | return [...new Array(props.numItems)].map((_, index) => { 61 | let x = randomize(width - props.itemDimensions.width); 62 | let y = -props.itemDimensions.height * 4; 63 | let anchor = randomize(width / 3, width / 12); 64 | let xArc = Math.abs(x - anchor); 65 | 66 | return { 67 | index, 68 | x: new Value(x), 69 | y: new Value(y), 70 | xArc: new Value(xArc), 71 | yBase: new Value(y), 72 | angle: new Value(0), 73 | anchor: new Value(anchor), 74 | tension: new Value(4 * Math.min(xArc, width/2) / width), 75 | xVel: new Value(randomize(xVelMax / 2, xVelMax / 2)), 76 | yVel: new Value(randomize(yVelMax, yVelMax)), 77 | angleVel: new Value(randomize(angleVelMax / 2, angleVelMax / 2)), 78 | delay: new Value((index / props.numItems) * height / yVelMax), 79 | color: props.itemColors[index % props.itemColors.length], 80 | } 81 | }); 82 | }, [dimensions]); 83 | } 84 | 85 | const spring = (dt, position, velocity, anchor, tension = 50, mass = 1 ) => { 86 | const dist = sub(position, anchor); 87 | const acc = divide(multiply(-1, tension, dist), mass); 88 | return set(velocity, add(velocity, multiply(dt, acc))); 89 | } 90 | 91 | const swingArc = (x, y, xArc, yBase, anchor ) => { 92 | const percentArc = divide(abs(sub(x, anchor)), xArc); 93 | const yOffset = multiply(percentArc, divide(xArc, 4)); 94 | return set(y, sub(yBase, yOffset)); 95 | } 96 | 97 | const useDraw = _items => { 98 | const nativeCode = useMemo(() => { 99 | const timeDiff = diff(clock); 100 | const nativeCode = _items.map(({ 101 | x, 102 | y, 103 | xArc, 104 | yBase, 105 | angle, 106 | xVel, 107 | yVel, 108 | angleVel, 109 | color, 110 | anchor, 111 | tension, 112 | delay, 113 | }) => { 114 | const dt = divide(timeDiff, 1000) 115 | const dy = multiply(dt, yVel) 116 | const dAngle = multiply(dt, angleVel) 117 | 118 | return [ 119 | cond( 120 | lessOrEq(yBase, containerDims.height + props.itemDimensions.height), 121 | cond( 122 | greaterThan(delay, 0), 123 | [set(delay, sub(delay, dt))], 124 | [ 125 | set(yBase, add(yBase, dy)), 126 | spring(dt, x, xVel, anchor, tension), // swinging motion 127 | set(x, add(x, multiply(xVel, dt))), 128 | swingArc(x, y, xArc, yBase, anchor ), // create dip in swing 129 | set(angle, add(angle, dAngle)), 130 | ] 131 | ) 132 | ), 133 | cond( 134 | eq(props.continuous, true), 135 | cond( 136 | greaterThan(yBase, containerDims.height + props.itemDimensions.height), 137 | set(yBase, -props.itemDimensions.height * 4), 138 | ) 139 | ), 140 | ]; 141 | }); 142 | 143 | nativeCode.push(cond(clockRunning(clock), 0, startClock(clock)), clock); 144 | return block(nativeCode); 145 | }, [_items]); 146 | 147 | useCode(() => nativeCode, [nativeCode]); 148 | }; 149 | 150 | useDraw(items); 151 | 152 | return ( 153 | 154 | {items.map( 155 | ({ index, x, y, angle, color: backgroundColor }) => { 156 | return ( 157 | 171 | { props.itemComponent } 172 | 173 | 174 | ) 175 | } 176 | )} 177 | 178 | ) 179 | } 180 | 181 | export default MakeItRain; 182 | 183 | const styles = StyleSheet.create({ 184 | animContainer: { 185 | position: 'absolute', 186 | top: 0, 187 | left: 0, 188 | alignItems: 'center', 189 | justifyContent: 'center', 190 | overflow: 'hidden', 191 | }, 192 | itemContainer: { 193 | position: 'absolute', 194 | top: 0, 195 | left: 0, 196 | } 197 | }) 198 | --------------------------------------------------------------------------------