├── src ├── types │ └── png.d.ts └── screens │ └── Main │ ├── components │ ├── PoofCard │ │ ├── assets │ │ │ ├── poof1.png │ │ │ ├── poof2.png │ │ │ ├── poof3.png │ │ │ ├── poof4.png │ │ │ └── poof5.png │ │ ├── styles.ts │ │ └── index.tsx │ ├── ShredCard │ │ ├── assets │ │ │ └── shredder.png │ │ ├── components │ │ │ └── Captured │ │ │ │ ├── styles.tsx │ │ │ │ └── index.tsx │ │ ├── styles.ts │ │ └── index.tsx │ ├── VanishCard │ │ ├── index.tsx │ │ └── styles.ts │ └── HoleCard │ │ ├── styles.ts │ │ └── index.tsx │ ├── styles.ts │ └── index.tsx ├── tsconfig.json ├── App.tsx ├── babel.config.js ├── README.md ├── package.json └── .gitignore /src/types/png.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.png" {} 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": {}, 3 | "extends": "expo/tsconfig.base" 4 | } 5 | -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | import Main from "./src/screens/Main"; 2 | 3 | export default function App() { 4 | return
; 5 | } 6 | -------------------------------------------------------------------------------- /src/screens/Main/components/PoofCard/assets/poof1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lklima/rn-transitions/HEAD/src/screens/Main/components/PoofCard/assets/poof1.png -------------------------------------------------------------------------------- /src/screens/Main/components/PoofCard/assets/poof2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lklima/rn-transitions/HEAD/src/screens/Main/components/PoofCard/assets/poof2.png -------------------------------------------------------------------------------- /src/screens/Main/components/PoofCard/assets/poof3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lklima/rn-transitions/HEAD/src/screens/Main/components/PoofCard/assets/poof3.png -------------------------------------------------------------------------------- /src/screens/Main/components/PoofCard/assets/poof4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lklima/rn-transitions/HEAD/src/screens/Main/components/PoofCard/assets/poof4.png -------------------------------------------------------------------------------- /src/screens/Main/components/PoofCard/assets/poof5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lklima/rn-transitions/HEAD/src/screens/Main/components/PoofCard/assets/poof5.png -------------------------------------------------------------------------------- /src/screens/Main/components/ShredCard/assets/shredder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lklima/rn-transitions/HEAD/src/screens/Main/components/ShredCard/assets/shredder.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ["babel-preset-expo"], 5 | plugins: ["react-native-reanimated/plugin"], 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RN Transitions 2 | 3 |

4 | 5 | Supports Expo iOS 6 | 7 | Supports Expo Android 8 |

9 | 10 | React Native animated app with reanimated + expo. 11 | 12 | Give me a ⭐️ if liked it and follow me. 13 | 14 | https://user-images.githubusercontent.com/44346970/175845652-96978d45-07cf-4412-b6ac-42bad317702c.mp4 15 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "expo": "^45.0.0", 4 | "react": "17.0.2", 5 | "react-dom": "17.0.2", 6 | "react-native": "0.68.2", 7 | "react-native-reanimated": "~2.8.0", 8 | "react-native-view-shot": "3.1.2", 9 | "react-native-web": "0.17.7", 10 | "styled-components": "^5.3.5" 11 | }, 12 | "devDependencies": { 13 | "@babel/core": "^7.12.9", 14 | "@types/react": "~17.0.21", 15 | "@types/react-native": "~0.67.6", 16 | "@types/styled-components-react-native": "^5.1.3", 17 | "typescript": "~4.3.5" 18 | }, 19 | "scripts": { 20 | "start": "expo start", 21 | "android": "expo start --android", 22 | "ios": "expo start --ios", 23 | "web": "expo start --web" 24 | }, 25 | "version": "1.0.0", 26 | "private": true, 27 | "resolutions": { 28 | "@types/react": "~17.0.21" 29 | }, 30 | "name": "rn-transitions" 31 | } 32 | -------------------------------------------------------------------------------- /src/screens/Main/components/PoofCard/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components/native"; 2 | import { Dimensions } from "react-native"; 3 | import Animated from "react-native-reanimated"; 4 | 5 | const { width } = Dimensions.get("window"); 6 | 7 | interface ContainerProps { 8 | index: number; 9 | } 10 | 11 | export const Container = styled.View` 12 | width: ${width / 2}px; 13 | align-items: center; 14 | justify-content: center; 15 | overflow: hidden; 16 | margin-bottom: 10px; 17 | padding-left: ${({ index }) => (index % 2 === 0 ? 10 : 0)}px; 18 | padding-right: ${({ index }) => (index % 2 === 0 ? 0 : 10)}px; 19 | `; 20 | 21 | export const CardView = styled(Animated.View)` 22 | height: ${width / 2 - 20}px; 23 | width: ${width / 2 - 20}px; 24 | background: #007aff; 25 | border-radius: 20px; 26 | `; 27 | 28 | export const Image = styled(Animated.Image)` 29 | height: 250px; 30 | width: 250px; 31 | position: absolute; 32 | opacity: 0; 33 | `; 34 | -------------------------------------------------------------------------------- /src/screens/Main/components/PoofCard/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { FadeIn } from "react-native-reanimated"; 3 | 4 | import * as S from "./styles"; 5 | 6 | import Poof1 from "./assets/poof1.png"; 7 | import Poof2 from "./assets/poof2.png"; 8 | import Poof3 from "./assets/poof3.png"; 9 | import Poof4 from "./assets/poof4.png"; 10 | import Poof5 from "./assets/poof5.png"; 11 | 12 | interface CardProps { 13 | index: number; 14 | } 15 | 16 | export default function PoofCard({ index }: CardProps) { 17 | return ( 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | *.hprof 33 | 34 | # node.js 35 | # 36 | node_modules/ 37 | npm-debug.log 38 | yarn-error.log 39 | 40 | # BUCK 41 | buck-out/ 42 | \.buckd/ 43 | *.keystore 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://docs.fastlane.tools/best-practices/source-control/ 51 | 52 | */fastlane/report.xml 53 | */fastlane/Preview.html 54 | */fastlane/screenshots 55 | 56 | # Bundle artifacts 57 | *.jsbundle 58 | 59 | # CocoaPods 60 | /ios/Pods/ 61 | 62 | # Expo 63 | .expo/* 64 | web-build/ 65 | -------------------------------------------------------------------------------- /src/screens/Main/components/ShredCard/components/Captured/styles.tsx: -------------------------------------------------------------------------------- 1 | import Animated from "react-native-reanimated"; 2 | import styled from "styled-components/native"; 3 | 4 | interface Props { 5 | width: number; 6 | height?: number; 7 | index?: number; 8 | dir?: number; 9 | shredWidth?: number; 10 | } 11 | 12 | export const Container = styled(Animated.View)` 13 | width: ${(props) => props.width}px; 14 | height: ${(props) => props.height}px; 15 | margin-bottom: 10px; 16 | position: absolute; 17 | opacity: 0; 18 | `; 19 | 20 | export const Slice = styled(Animated.View)` 21 | max-width: ${(props) => props.width}px; 22 | max-width: ${(props) => props.width}px; 23 | position: absolute; 24 | bottom: 0; 25 | top: 0; 26 | left: ${(props) => props.width * props.index}px; 27 | overflow: hidden; 28 | background: white; 29 | `; 30 | 31 | export const SliceImage = styled.Image` 32 | position: absolute; 33 | top: 0; 34 | bottom: 0; 35 | left: ${(props) => props.shredWidth * -props.index}px; 36 | opacity: ${(props) => (props.dir === 1 ? 1 : 1.0 - 0.2 * 1)}; 37 | width: ${(props) => props.width}px; 38 | height: ${(props) => props.height}px; 39 | `; 40 | -------------------------------------------------------------------------------- /src/screens/Main/components/VanishCard/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { FadeIn, FlipInEasyX, ZoomOut } from "react-native-reanimated"; 3 | import { useWindowDimensions } from "react-native"; 4 | 5 | import * as S from "./styles"; 6 | 7 | interface CardProps { 8 | index: number; 9 | } 10 | 11 | export default function PoofCard({ index }: CardProps) { 12 | const { width } = useWindowDimensions(); 13 | 14 | const size = width / 2 - 20; 15 | const particlesAmount = 150; 16 | const particles = Array.from({ length: particlesAmount }, (_, index) => index); 17 | 18 | function renderParticles() { 19 | return particles.map((particle) => { 20 | const ramdomTop = Math.random() * size - 10; 21 | const ramdomLeft = Math.random() * size - 10; 22 | 23 | return ( 24 | 30 | ); 31 | }); 32 | } 33 | 34 | return ( 35 | 36 | 37 | 38 | {renderParticles()} 39 | 40 | 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/screens/Main/components/VanishCard/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components/native"; 2 | import { Dimensions } from "react-native"; 3 | import Animated from "react-native-reanimated"; 4 | 5 | const { width } = Dimensions.get("window"); 6 | 7 | interface ContainerProps { 8 | index: number; 9 | } 10 | 11 | interface ParticleProps { 12 | top: number; 13 | left: number; 14 | } 15 | 16 | export const Container = styled.View` 17 | width: ${width / 2}px; 18 | align-items: center; 19 | justify-content: center; 20 | margin-bottom: 10px; 21 | padding-left: ${({ index }) => (index % 2 === 0 ? 10 : 0)}px; 22 | padding-right: ${({ index }) => (index % 2 === 0 ? 0 : 10)}px; 23 | `; 24 | 25 | export const CardView = styled(Animated.View)` 26 | height: ${width / 2 - 20}px; 27 | width: ${width / 2 - 20}px; 28 | background: #007aff; 29 | border-radius: 20px; 30 | z-index: 999; 31 | `; 32 | 33 | export const ParticleContent = styled(Animated.View)` 34 | height: ${width / 2 - 20}px; 35 | width: ${width / 2 - 20}px; 36 | position: absolute; 37 | flex-direction: row; 38 | flex-wrap: wrap; 39 | opacity: 0; 40 | `; 41 | 42 | export const Particle = styled(Animated.View)` 43 | height: 30px; 44 | width: 30px; 45 | background: #007aff; 46 | border-radius: 30px; 47 | opacity: 0.3; 48 | position: absolute; 49 | `; 50 | -------------------------------------------------------------------------------- /src/screens/Main/components/ShredCard/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components/native"; 2 | import { Dimensions } from "react-native"; 3 | import Animated from "react-native-reanimated"; 4 | 5 | const { width } = Dimensions.get("window"); 6 | 7 | interface ContainerProps { 8 | index: number; 9 | } 10 | 11 | export const Container = styled.View` 12 | width: ${width / 2}px; 13 | align-items: center; 14 | justify-content: center; 15 | margin-bottom: 10px; 16 | padding-left: ${({ index }) => (index % 2 === 0 ? 10 : 0)}px; 17 | padding-right: ${({ index }) => (index % 2 === 0 ? 0 : 10)}px; 18 | `; 19 | 20 | export const CardView = styled(Animated.View)` 21 | height: ${width / 2 - 20}px; 22 | width: ${width / 2 - 20}px; 23 | background: ${({ index }) => (index % 2 === 0 ? "#ffab00" : "#007aff")}; 24 | border-radius: 20px; 25 | align-items: center; 26 | justify-content: center; 27 | overflow: hidden; 28 | `; 29 | 30 | export const CardText = styled.Text` 31 | font-size: 35px; 32 | font-weight: bold; 33 | color: white; 34 | text-align: center; 35 | `; 36 | 37 | export const Shredder = styled(Animated.Image)` 38 | width: ${width / 2 - 2}px; 39 | position: absolute; 40 | z-index: 9999; 41 | align-self: center; 42 | left: ${({ index }) => (index % 2 === 0 ? 5 : -3)}px; 43 | opacity: 0; 44 | `; 45 | -------------------------------------------------------------------------------- /src/screens/Main/components/HoleCard/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from "styled-components/native"; 2 | import { Dimensions } from "react-native"; 3 | import Animated from "react-native-reanimated"; 4 | 5 | const { width } = Dimensions.get("window"); 6 | 7 | interface ContainerProps { 8 | index: number; 9 | } 10 | 11 | export const Container = styled.View` 12 | width: ${width / 2}px; 13 | align-items: center; 14 | overflow: hidden; 15 | margin-top: -20px; 16 | margin-bottom: -40px; 17 | padding-left: ${({ index }) => (index % 2 === 0 ? 10 : 0)}px; 18 | padding-right: ${({ index }) => (index % 2 === 0 ? 0 : 10)}px; 19 | `; 20 | 21 | export const CardHidder = styled.View` 22 | align-items: center; 23 | padding: 0 60px; 24 | padding-bottom: 60px; 25 | padding-top: 20px; 26 | margin-bottom: -32px; 27 | border-bottom-left-radius: 110px; 28 | border-bottom-right-radius: 110px; 29 | overflow: hidden; 30 | z-index: 999; 31 | `; 32 | 33 | export const CardView = styled(Animated.View)` 34 | height: ${width / 2 - 20}px; 35 | width: ${width / 2 - 20}px; 36 | align-items: center; 37 | justify-content: center; 38 | background: #007aff; 39 | border-radius: 20px; 40 | `; 41 | 42 | export const Text = styled.Text` 43 | color: white; 44 | font-size: 30px; 45 | text-align: center; 46 | `; 47 | 48 | export const HoleContainer = styled(Animated.View)` 49 | transform: scale(0); 50 | `; 51 | 52 | export const HoleBorder = styled.View` 53 | width: 33px; 54 | height: 33px; 55 | background: gray; 56 | border-radius: 50px; 57 | transform: scaleX(6); 58 | overflow: hidden; 59 | `; 60 | 61 | export const Hole = styled.View` 62 | width: 33px; 63 | height: 33px; 64 | background: black; 65 | border-radius: 50px; 66 | margin-top: 7px; 67 | `; 68 | -------------------------------------------------------------------------------- /src/screens/Main/components/HoleCard/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { withSequence, withTiming, FlipInEasyX } from "react-native-reanimated"; 3 | 4 | import * as S from "./styles"; 5 | 6 | interface CardProps { 7 | index: number; 8 | } 9 | 10 | export default function Card({ index }: CardProps) { 11 | function cardExiting() { 12 | "worklet"; 13 | const animations = { 14 | transform: [ 15 | { translateY: withSequence(withTiming(-10), withTiming(250, { duration: 600 })) }, 16 | { 17 | rotateZ: withSequence( 18 | withTiming("5deg"), 19 | withTiming("0deg", { duration: 600 }) 20 | ), 21 | }, 22 | ], 23 | }; 24 | const initialValues = { 25 | transform: [{ translateY: 0 }, { rotateZ: "0deg" }], 26 | }; 27 | return { 28 | initialValues, 29 | animations, 30 | }; 31 | } 32 | 33 | function holeExiting() { 34 | "worklet"; 35 | const animations = { 36 | transform: [ 37 | { 38 | scale: withSequence( 39 | withTiming(1, { duration: 400 }), 40 | withTiming(1, { duration: 600 }), 41 | withTiming(0, { duration: 400 }) 42 | ), 43 | }, 44 | ], 45 | }; 46 | const initialValues = { 47 | transform: [{ scale: 0 }], 48 | }; 49 | return { 50 | initialValues, 51 | animations, 52 | }; 53 | } 54 | 55 | return ( 56 | 57 | 58 | 59 | Hello{"\n"}World! 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /src/screens/Main/styles.ts: -------------------------------------------------------------------------------- 1 | import { Dimensions } from "react-native"; 2 | import Animated from "react-native-reanimated"; 3 | import styled from "styled-components/native"; 4 | 5 | const { width } = Dimensions.get("window"); 6 | 7 | interface TextPops { 8 | selected: boolean; 9 | } 10 | 11 | export const Container = styled.SafeAreaView` 12 | flex: 1; 13 | align-items: center; 14 | `; 15 | 16 | export const OptionsContent = styled.View` 17 | width: ${width - 35}px; 18 | height: 80px; 19 | border-radius: 8px; 20 | background: #eeedee; 21 | overflow: hidden; 22 | flex-direction: row; 23 | margin-top: 20px; 24 | `; 25 | 26 | export const Highlight = styled(Animated.View)` 27 | height: ${(width - 35) / 3}px; 28 | width: ${(width - 35) / 3}px; 29 | background: #007aff; 30 | position: absolute; 31 | `; 32 | 33 | export const Option = styled.TouchableOpacity.attrs({ 34 | activeOpacity: 0.8, 35 | })` 36 | flex: 1; 37 | align-items: center; 38 | justify-content: center; 39 | `; 40 | 41 | export const OptionText = styled.Text` 42 | font-size: 20px; 43 | color: ${({ selected }) => (selected ? "white" : "#007aff")}; 44 | `; 45 | 46 | export const RowContent = styled.View` 47 | width: 90%; 48 | flex-direction: row; 49 | align-items: center; 50 | justify-content: space-between; 51 | margin-top: 15px; 52 | `; 53 | 54 | export const Text = styled.Text` 55 | font-size: 20px; 56 | `; 57 | 58 | export const Count = styled.Text` 59 | font-size: 20px; 60 | color: gray; 61 | `; 62 | 63 | export const AddButtonsContent = styled.View` 64 | flex-direction: row; 65 | width: 100px; 66 | align-items: center; 67 | justify-content: center; 68 | border-radius: 8px; 69 | background: #eeedee; 70 | height: 40px; 71 | `; 72 | 73 | export const Button = styled.TouchableOpacity` 74 | flex: 1; 75 | align-items: center; 76 | `; 77 | 78 | export const CardContent = styled.View` 79 | width: 100%; 80 | flex-direction: row; 81 | justify-content: space-between; 82 | flex-wrap: wrap; 83 | margin-top: 20px; 84 | `; 85 | -------------------------------------------------------------------------------- /src/screens/Main/components/ShredCard/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from "react"; 2 | import { FlipInEasyX, withSequence, withTiming } from "react-native-reanimated"; 3 | import { useWindowDimensions } from "react-native"; 4 | 5 | import * as S from "./styles"; 6 | 7 | import Captured from "./components/Captured"; 8 | 9 | import shredder from "./assets/shredder.png"; 10 | 11 | interface CardProps { 12 | index: number; 13 | } 14 | 15 | export default function ShredCard({ index }: CardProps) { 16 | const { width } = useWindowDimensions(); 17 | const capture = useRef(null); 18 | 19 | function cardExiting() { 20 | "worklet"; 21 | const animations = { 22 | transform: [ 23 | { 24 | translateY: withSequence( 25 | withTiming(2, { duration: 50 }), 26 | withTiming(-2, { duration: 50 }), 27 | withTiming(20, { duration: 200 }), 28 | withTiming(20, { duration: 200 }), 29 | withTiming(140, { duration: 600 }), 30 | withTiming(140, { duration: 100 }), 31 | withTiming(135, { duration: 50 }), 32 | withTiming(200, { duration: 200 }) 33 | ), 34 | }, 35 | ], 36 | height: withTiming(0, { duration: 1200 }), 37 | }; 38 | const initialValues = { 39 | transform: [{ translateY: 0 }], 40 | height: width / 2 - 20, 41 | }; 42 | return { 43 | initialValues, 44 | animations, 45 | }; 46 | } 47 | 48 | function shredExiting() { 49 | "worklet"; 50 | const animations = { 51 | transform: [ 52 | { 53 | translateY: withSequence( 54 | withTiming(30, { duration: 1000 }), 55 | withTiming(33, { duration: 100 }), 56 | withTiming(30, { duration: 100 }) 57 | ), 58 | }, 59 | ], 60 | opacity: withSequence( 61 | withTiming(1, { duration: 200 }), 62 | withTiming(1, { duration: 1300 }), 63 | withTiming(0, { duration: 200 }) 64 | ), 65 | }; 66 | const initialValues = { 67 | transform: [{ translateY: 70 }], 68 | opacity: 0, 69 | }; 70 | return { 71 | initialValues, 72 | animations, 73 | }; 74 | } 75 | 76 | return ( 77 | 78 | 84 | 85 | 91 | Hello{"\n"}World 92 | 93 | 94 | ); 95 | } 96 | -------------------------------------------------------------------------------- /src/screens/Main/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { AntDesign } from "@expo/vector-icons"; 3 | import { useWindowDimensions } from "react-native"; 4 | import { 5 | useAnimatedStyle, 6 | useSharedValue, 7 | interpolate, 8 | withSpring, 9 | } from "react-native-reanimated"; 10 | 11 | import * as S from "./styles"; 12 | 13 | import HoleCard from "./components/HoleCard"; 14 | import PoofCard from "./components/PoofCard"; 15 | import ShredCard from "./components/ShredCard"; 16 | 17 | export default function Main() { 18 | const { width } = useWindowDimensions(); 19 | 20 | const highlightTranlateX = useSharedValue(0); 21 | 22 | const [count, setCount] = useState(0); 23 | const [optionSelected, setOptionSelected] = useState(0); 24 | 25 | const cards = Array.from({ length: count }, (_, index) => index); 26 | 27 | function handleCard(type: string) { 28 | if (type === "add") { 29 | setCount(count + 1); 30 | } else { 31 | if (count > 0) { 32 | setCount(count - 1); 33 | } 34 | } 35 | } 36 | 37 | useEffect(() => { 38 | const highlightWidth = (width - 35) / 3; 39 | 40 | highlightTranlateX.value = withSpring( 41 | interpolate(optionSelected, [0, 1, 2], [0, highlightWidth, highlightWidth * 2]), 42 | { damping: 14 } 43 | ); 44 | }, [optionSelected]); 45 | 46 | const highlightAnimatedStyle = useAnimatedStyle(() => ({ 47 | transform: [{ translateX: highlightTranlateX.value }], 48 | })); 49 | 50 | function renderCards() { 51 | if (optionSelected === 0) { 52 | return cards.map((card) => ); 53 | } else if (optionSelected === 1) { 54 | return cards.map((card) => ); 55 | } 56 | 57 | return cards.map((card) => ); 58 | } 59 | 60 | return ( 61 | 62 | 63 | 64 | setOptionSelected(0)}> 65 | Shredded 66 | 67 | setOptionSelected(1)}> 68 | Hole 69 | 70 | setOptionSelected(2)}> 71 | Poof 72 | 73 | 74 | 75 | 76 | View Count ({count}) 77 | 78 | 79 | handleCard("remove")}> 80 | 81 | 82 | handleCard("add")}> 83 | 84 | 85 | 86 | 87 | {renderCards()} 88 | 89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /src/screens/Main/components/ShredCard/components/Captured/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useWindowDimensions } from "react-native"; 3 | import { withDelay, withSequence, withTiming } from "react-native-reanimated"; 4 | import { captureRef } from "react-native-view-shot"; 5 | 6 | import { Container, Slice, SliceImage } from "./styles"; 7 | 8 | interface Props { 9 | capture: any; 10 | } 11 | 12 | export default function Captured({ capture }: Props) { 13 | const { width } = useWindowDimensions(); 14 | 15 | const [img, setImg] = useState(null); 16 | 17 | const size = width / 2 - 20; 18 | const shredCount = 16; 19 | const shredWidth = size / shredCount; 20 | 21 | useEffect(() => { 22 | if (capture.current && width) { 23 | captureRef(capture, { width: size, height: size }).then(setImg); 24 | } 25 | }, [capture, width]); 26 | 27 | function cardExiting() { 28 | "worklet"; 29 | const animations = { 30 | transform: [ 31 | { 32 | translateY: withSequence( 33 | withTiming(5, { duration: 100 }), 34 | withTiming(25, { duration: 200 }), 35 | withTiming(25, { duration: 200 }), 36 | withTiming(140, { duration: 600 }), 37 | withTiming(140, { duration: 100 }), 38 | withTiming(135, { duration: 50 }), 39 | withTiming(200, { duration: 200 }) 40 | ), 41 | }, 42 | ], 43 | opacity: withDelay(100, withTiming(1)), 44 | }; 45 | const initialValues = { 46 | transform: [{ translateY: 0 }], 47 | opacity: 0, 48 | }; 49 | return { 50 | initialValues, 51 | animations, 52 | }; 53 | } 54 | 55 | if (!img) return <>; 56 | 57 | return ( 58 | 59 | {new Array(shredCount).fill(0).map((_, index) => { 60 | // Alternate direction 61 | const fromMiddle = index - shredCount / 2; 62 | const dir = index % 2 === 0 ? 1 : -1; 63 | 64 | return ( 65 | { 70 | "worklet"; 71 | const animations = { 72 | transform: [ 73 | { perspective: withTiming(400, { duration: 800 }) }, 74 | { 75 | rotateZ: withTiming(index % 2 === 0 ? "-2deg" : "2deg", { 76 | duration: 1200, 77 | }), 78 | }, 79 | { 80 | rotateZ: withSequence( 81 | withTiming(-fromMiddle, { 82 | duration: 1200, 83 | }), 84 | withTiming(index % 2 === 0 ? -fromMiddle * 9 : fromMiddle * 9, { 85 | duration: 400, 86 | }) 87 | ), 88 | }, 89 | { 90 | rotateX: withTiming(index % 2 === 0 ? "-15deg" : "15deg", { 91 | duration: 1200, 92 | }), 93 | }, 94 | ], 95 | opacity: withDelay(1300, withTiming(0)), 96 | }; 97 | const initialValues = { 98 | transform: [ 99 | { perspective: 0 }, 100 | { rotateZ: "0deg" }, 101 | { rotateZ: "0deg" }, 102 | { rotateX: "0deg" }, 103 | ], 104 | opacity: 1, 105 | }; 106 | return { 107 | initialValues, 108 | animations, 109 | }; 110 | }} 111 | > 112 | 120 | 121 | ); 122 | })} 123 | 124 | ); 125 | } 126 | --------------------------------------------------------------------------------