├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── gifs ├── fade-in.gif ├── flip-x.gif ├── flip-y.gif ├── from-left.gif ├── from-top.gif ├── zoom-in.gif └── zoom-out.gif ├── package-lock.json ├── package.json └── src ├── index.d.ts └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | gifs/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Phil Mok 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-navigation-transitions 2 | 3 | ### Installation 4 | `npm install react-navigation-transitions --save` 5 | 6 | ### Instructions 7 | These functions are meant to be used as the `transitionConfig` with [react-navigation](https://reactnavigation.org/). So far it includes the following transitions: 8 | 9 | `fromLeft` 10 | 11 | `fromTop` 12 | 13 | `fromRight` 14 | 15 | `fromBottom` 16 | 17 | `fadeIn` 18 | 19 | `fadeout` 20 | 21 | `zoomIn` 22 | 23 | `zoomOut` 24 | 25 | `flipY` 26 | 27 | `flipX` 28 | 29 | More will be added in future versions. 30 | 31 | ### Example 32 | 33 | ```javascript 34 | import { createStackNavigator } from 'react-navigation'; 35 | import { fromLeft } from 'react-navigation-transitions'; 36 | 37 | const appStack = createStackNavigator( 38 | { 39 | ScreenA: { 40 | screen: ScreenA, 41 | }, 42 | ScreenB: { 43 | screen: ScreenB, 44 | }, 45 | }, 46 | { 47 | initialRouteName: 'ScreenA', 48 | transitionConfig: () => fromLeft(), 49 | }, 50 | ); 51 | ``` 52 | 53 | The default duration is 300 milliseconds but you can pass is a custom transition duration like so: 54 | 55 | ```javascript 56 | transitionConfig: () => fromLeft(1000), 57 | ``` 58 | 59 | ## Adding transitions to specific screens 60 | 61 | In the example above, the same transition animation will be used for all screens within the 62 | navigator. The example below shows how to use different transitions for specific screens. 63 | 64 | Thank you to [SebLambla](https://github.com/SebLambla) for coming up with this great example. 65 | 66 | ```javascript 67 | import { fromLeft, zoomIn, zoomOut } from 'react-navigation-transitions' 68 | 69 | const handleCustomTransition = ({ scenes }) => { 70 | const prevScene = scenes[scenes.length - 2]; 71 | const nextScene = scenes[scenes.length - 1]; 72 | 73 | // Custom transitions go there 74 | if (prevScene 75 | && prevScene.route.routeName === 'ScreenA' 76 | && nextScene.route.routeName === 'ScreenB') { 77 | return zoomIn(); 78 | } else if (prevScene 79 | && prevScene.route.routeName === 'ScreenB' 80 | && nextScene.route.routeName === 'ScreenC') { 81 | return zoomOut(); 82 | } 83 | return fromLeft(); 84 | } 85 | 86 | const PrimaryNav = createStackNavigator({ 87 | ScreenA: { screen: ScreenA }, 88 | ScreenB: { screen: ScreenB }, 89 | ScreenC: { screen: ScreenC }, 90 | }, { 91 | transitionConfig: (nav) => handleCustomTransition(nav) 92 | }) 93 | ``` 94 | 95 | ## Use with SafeAreaView 96 | 97 | If you are having trouble with `SafeAreaView` like in this [issue](https://github.com/plmok61/react-navigation-transitions/issues/11), try adding `forceInset={{ top: 'always', bottom: 'always' }}` 98 | 99 | ## GIFS 100 | 101 | ### fromLeft 102 | 103 | ![](./gifs/from-left.gif) 104 | 105 | ### fromTop 106 | 107 | ![](./gifs/from-top.gif) 108 | 109 | ### fadeIn 110 | 111 | ![](./gifs/fade-in.gif) 112 | 113 | ### zoomIn 114 | 115 | ![](./gifs/zoom-in.gif) 116 | 117 | ### zoomOut 118 | 119 | ![](./gifs/zoom-out.gif) 120 | 121 | ### flipY 122 | 123 | ![](./gifs/flip-y.gif) 124 | 125 | ### flipX 126 | 127 | ![](./gifs/flip-x.gif) 128 | 129 | The basis for these functions can be found in the `react-navigation` docs [here](https://reactnavigation.org/docs/stack-navigator.html#modal-stacknavigator-with-custom-screen-transitions). 130 | -------------------------------------------------------------------------------- /gifs/fade-in.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plmok61/react-navigation-transitions/5c855271a41be6af1513b4c6ebdce930509cc95d/gifs/fade-in.gif -------------------------------------------------------------------------------- /gifs/flip-x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plmok61/react-navigation-transitions/5c855271a41be6af1513b4c6ebdce930509cc95d/gifs/flip-x.gif -------------------------------------------------------------------------------- /gifs/flip-y.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plmok61/react-navigation-transitions/5c855271a41be6af1513b4c6ebdce930509cc95d/gifs/flip-y.gif -------------------------------------------------------------------------------- /gifs/from-left.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plmok61/react-navigation-transitions/5c855271a41be6af1513b4c6ebdce930509cc95d/gifs/from-left.gif -------------------------------------------------------------------------------- /gifs/from-top.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plmok61/react-navigation-transitions/5c855271a41be6af1513b4c6ebdce930509cc95d/gifs/from-top.gif -------------------------------------------------------------------------------- /gifs/zoom-in.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plmok61/react-navigation-transitions/5c855271a41be6af1513b4c6ebdce930509cc95d/gifs/zoom-in.gif -------------------------------------------------------------------------------- /gifs/zoom-out.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plmok61/react-navigation-transitions/5c855271a41be6af1513b4c6ebdce930509cc95d/gifs/zoom-out.gif -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-navigation-transitions", 3 | "version": "1.0.12", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/prop-types": { 8 | "version": "15.7.1", 9 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz", 10 | "integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg==", 11 | "dev": true 12 | }, 13 | "@types/react": { 14 | "version": "16.8.23", 15 | "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.23.tgz", 16 | "integrity": "sha512-abkEOIeljniUN9qB5onp++g0EY38h7atnDHxwKUFz1r3VH1+yG1OKi2sNPTyObL40goBmfKFpdii2lEzwLX1cA==", 17 | "dev": true, 18 | "requires": { 19 | "@types/prop-types": "*", 20 | "csstype": "^2.2.0" 21 | } 22 | }, 23 | "@types/react-native": { 24 | "version": "0.60.2", 25 | "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.60.2.tgz", 26 | "integrity": "sha512-K4+/etirpv52xu24xAc++OdhbD0VQEt0Kq0h6dZrLU82OlA+I7BhpKF3JBvx9tbmrFaZDxhHp8N4TEvRYS4fdQ==", 27 | "dev": true, 28 | "requires": { 29 | "@types/prop-types": "*", 30 | "@types/react": "*" 31 | } 32 | }, 33 | "@types/react-navigation": { 34 | "version": "3.0.7", 35 | "resolved": "https://registry.npmjs.org/@types/react-navigation/-/react-navigation-3.0.7.tgz", 36 | "integrity": "sha512-JFsNeCAQEQGTpObiD1QczDyCyQkFBaQA/F85Fo2W8QML1b6UNYHlUDtEYJA6jcfXqPoL15dVPBVj9NBJlHToqA==", 37 | "dev": true, 38 | "requires": { 39 | "@types/react": "*", 40 | "@types/react-native": "*" 41 | } 42 | }, 43 | "csstype": { 44 | "version": "2.6.6", 45 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.6.tgz", 46 | "integrity": "sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg==", 47 | "dev": true 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-navigation-transitions", 3 | "version": "1.0.12", 4 | "description": "Custom transitions for react-navigation", 5 | "author": "Phil Mok", 6 | "main": "src/index.js", 7 | "typings": "src/index.d.ts", 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/plmok61/react-navigation-transitions.git" 14 | }, 15 | "keywords": [ 16 | "react-native", 17 | "react", 18 | "react-navigation", 19 | "navigation", 20 | "transitions", 21 | "animations" 22 | ], 23 | "peerDependencies": { 24 | "react": "*", 25 | "react-native": "*", 26 | "react-navigation": "*" 27 | }, 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/plmok61/react-navigation-transitions/issues" 31 | }, 32 | "homepage": "https://github.com/plmok61/react-navigation-transitions#readme", 33 | "devDependencies": { 34 | "@types/react-navigation": "^3.0.7" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { TransitionConfig } from "react-navigation"; 2 | export declare function fromLeft(duration?: number): TransitionConfig; 3 | export declare function fromTop(duration?: number): TransitionConfig; 4 | export declare function fromRight(duration?: number): TransitionConfig; 5 | export declare function fromBottom(duration?: number): TransitionConfig; 6 | export declare function fadeIn(duration?: number): TransitionConfig; 7 | export declare function fadeOut(duration?: number): TransitionConfig; 8 | export declare function zoomIn(duration?: number): TransitionConfig; 9 | export declare function zoomOut(duration?: number): TransitionConfig; 10 | export declare function flipY(duration?: number): TransitionConfig; 11 | export declare function flipX(duration?: number): TransitionConfig; 12 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { Animated, Easing, Platform } from 'react-native'; 2 | 3 | export function fromLeft(duration = 300) { 4 | return { 5 | transitionSpec: { 6 | duration, 7 | easing: Easing.out(Easing.poly(4)), 8 | timing: Animated.timing, 9 | useNativeDriver: true, 10 | }, 11 | screenInterpolator: ({ layout, position, scene }) => { 12 | const { index } = scene; 13 | const { initWidth } = layout; 14 | 15 | const translateX = position.interpolate({ 16 | inputRange: [index - 1, index, index + 1], 17 | outputRange: [-initWidth, 0, 0], 18 | }); 19 | 20 | const opacity = position.interpolate({ 21 | inputRange: [index - 1, index - 0.99, index], 22 | outputRange: [0, 1, 1], 23 | }); 24 | 25 | return { opacity, transform: [{ translateX }] }; 26 | }, 27 | }; 28 | } 29 | 30 | export function fromTop(duration = 300) { 31 | return { 32 | transitionSpec: { 33 | duration, 34 | easing: Easing.out(Easing.poly(4)), 35 | timing: Animated.timing, 36 | useNativeDriver: true, 37 | }, 38 | screenInterpolator: ({ layout, position, scene }) => { 39 | const { index } = scene; 40 | const { initHeight } = layout; 41 | 42 | const translateY = position.interpolate({ 43 | inputRange: [index - 1, index, index + 1], 44 | outputRange: [-initHeight, 0, 0], 45 | }); 46 | 47 | const opacity = position.interpolate({ 48 | inputRange: [index - 1, index - 0.99, index], 49 | outputRange: [0, 1, 1], 50 | }); 51 | 52 | return { opacity, transform: [{ translateY }] }; 53 | }, 54 | }; 55 | } 56 | 57 | export function fromRight(duration = 300) { 58 | return { 59 | transitionSpec: { 60 | duration, 61 | easing: Easing.out(Easing.poly(4)), 62 | timing: Animated.timing, 63 | useNativeDriver: true, 64 | }, 65 | screenInterpolator: ({ layout, position, scene }) => { 66 | const { index } = scene; 67 | const { initWidth } = layout; 68 | 69 | const translateX = position.interpolate({ 70 | inputRange: [index - 1, index, index + 1], 71 | outputRange: [initWidth, 0, 0], 72 | }); 73 | 74 | const opacity = position.interpolate({ 75 | inputRange: [index - 1, index - 0.99, index], 76 | outputRange: [0, 1, 1], 77 | }); 78 | 79 | return { opacity, transform: [{ translateX }] }; 80 | }, 81 | }; 82 | } 83 | 84 | export function fromBottom(duration = 300) { 85 | return { 86 | transitionSpec: { 87 | duration, 88 | easing: Easing.out(Easing.poly(4)), 89 | timing: Animated.timing, 90 | useNativeDriver: true, 91 | }, 92 | screenInterpolator: ({ layout, position, scene }) => { 93 | const { index } = scene; 94 | const { initHeight } = layout; 95 | 96 | const translateY = position.interpolate({ 97 | inputRange: [index - 1, index, index + 1], 98 | outputRange: [initHeight, 0, 0], 99 | }); 100 | 101 | const opacity = position.interpolate({ 102 | inputRange: [index - 1, index - 0.99, index], 103 | outputRange: [0, 1, 1], 104 | }); 105 | 106 | return { opacity, transform: [{ translateY }] }; 107 | }, 108 | }; 109 | } 110 | 111 | export function fadeIn(duration = 300) { 112 | return { 113 | transitionSpec: { 114 | duration, 115 | easing: Easing.out(Easing.poly(4)), 116 | timing: Animated.timing, 117 | useNativeDriver: true, 118 | }, 119 | screenInterpolator: ({ position, scene }) => { 120 | const { index } = scene; 121 | 122 | const opacity = position.interpolate({ 123 | inputRange: [index - 1, index], 124 | outputRange: [0, 1], 125 | }); 126 | 127 | return { opacity }; 128 | }, 129 | }; 130 | } 131 | 132 | export function fadeOut(duration = 300) { 133 | return { 134 | transitionSpec: { 135 | duration, 136 | easing: Easing.out(Easing.poly(4)), 137 | timing: Animated.timing, 138 | useNativeDriver: true, 139 | }, 140 | screenInterpolator: ({ position, scene }) => { 141 | const { index } = scene; 142 | 143 | const opacity = position.interpolate({ 144 | inputRange: [index - 1, index, index + 1], 145 | outputRange: [0, 1, 0] 146 | }); 147 | 148 | return { opacity }; 149 | }, 150 | }; 151 | } 152 | 153 | export function zoomIn(duration = 300) { 154 | return { 155 | transitionSpec: { 156 | duration, 157 | easing: Easing.out(Easing.poly(4)), 158 | timing: Animated.timing, 159 | useNativeDriver: true, 160 | }, 161 | screenInterpolator: ({ position, scene }) => { 162 | const { index } = scene; 163 | let start = 0; 164 | 165 | if (Platform.OS !== 'ios') { 166 | start = 0.005 167 | } 168 | 169 | const scale = position.interpolate({ 170 | inputRange: [index - 1, index], 171 | outputRange: [start, 1], 172 | }); 173 | 174 | return { transform: [{ scale }] }; 175 | }, 176 | }; 177 | } 178 | 179 | export function zoomOut(duration = 300) { 180 | return { 181 | transitionSpec: { 182 | duration, 183 | easing: Easing.out(Easing.poly(4)), 184 | timing: Animated.timing, 185 | useNativeDriver: true, 186 | }, 187 | screenInterpolator: ({ position, scene }) => { 188 | const { index } = scene; 189 | 190 | const scale = position.interpolate({ 191 | inputRange: [index - 1, index], 192 | outputRange: [10, 1], 193 | }); 194 | 195 | return { transform: [{ scale }] }; 196 | }, 197 | }; 198 | } 199 | 200 | export function flipY(duration = 300) { 201 | return { 202 | transitionSpec: { 203 | duration, 204 | easing: Easing.out(Easing.poly(4)), 205 | timing: Animated.timing, 206 | useNativeDriver: true, 207 | }, 208 | screenInterpolator: ({ position, scene }) => { 209 | const { index } = scene; 210 | 211 | const rotateY = position.interpolate({ 212 | inputRange: [index - 1, index], 213 | outputRange: ['180deg', '0deg'], 214 | }); 215 | 216 | return { transform: [{ rotateY }], backfaceVisibility: 'hidden' }; 217 | }, 218 | }; 219 | } 220 | 221 | export function flipX(duration = 300) { 222 | return { 223 | transitionSpec: { 224 | duration, 225 | easing: Easing.out(Easing.poly(4)), 226 | timing: Animated.timing, 227 | useNativeDriver: true, 228 | }, 229 | screenInterpolator: ({ position, scene }) => { 230 | const { index } = scene; 231 | 232 | const rotateX = position.interpolate({ 233 | inputRange: [index - 1, index], 234 | outputRange: ['180deg', '0deg'], 235 | }); 236 | 237 | return { transform: [{ rotateX }], backfaceVisibility: 'hidden' }; 238 | }, 239 | }; 240 | } 241 | --------------------------------------------------------------------------------