├── .DS_Store ├── .expo-shared └── assets.json ├── .github └── FUNDING.yml ├── .gitignore ├── .watchmanconfig ├── App.js ├── README.md ├── app.json ├── assets ├── icon.png ├── mars_planet.png └── splash.png ├── babel.config.js ├── package.json ├── react-native-shared-element-transition.png └── yarn.lock /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catalinmiron/react-native-shared-element-transition/b07fd26484e7ca1f37a6e9d6b1e2b7d7d7ecd08b/.DS_Store -------------------------------------------------------------------------------- /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "f9155ac790fd02fadcdeca367b02581c04a353aa6d5aa84409a59f6804c87acd": true, 3 | "89ed26367cdb9b771858e026f2eb95bfdb90e5ae943e716575327ec325f39c44": true 4 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: catalinmiron # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: catalinmiron # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://www.buymeacoffee.com/catalinmiron']# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | // Inspiration: https://dribbble.com/shots/6520262-Mars 2 | // Planet image: https://pngimg.com/download/61156 3 | import React from 'react'; 4 | import { 5 | ScrollView, 6 | Dimensions, 7 | TouchableOpacity, 8 | TouchableWithoutFeedback, 9 | StyleSheet, 10 | Text, 11 | View, 12 | Image, 13 | Platform, 14 | StatusBar 15 | } from 'react-native'; 16 | import { AppLoading } from 'expo'; 17 | import { Asset } from 'expo-asset'; 18 | import { createAppContainer, createStackNavigator } from 'react-navigation'; 19 | import { Ionicons } from '@expo/vector-icons'; 20 | import * as Animatable from 'react-native-animatable'; 21 | import { createFluidNavigator, Transition } from 'react-navigation-fluid-transitions'; 22 | const { width, height } = Dimensions.get('screen'); 23 | 24 | const smallPlanet = 300; 25 | const bigPlanet = 900; 26 | const ratio = bigPlanet / smallPlanet; 27 | const dotSize = 14; 28 | const rotation = '60deg'; 29 | 30 | const numberOfLocations = 10; 31 | const timings = [...Array(numberOfLocations).keys()].map(() => Math.round(Math.random() * 80) + 10); 32 | const coordinates = [...Array(numberOfLocations).keys()].map(() => { 33 | const angle = Math.random() * 2 * Math.PI; 34 | const maxRadius = smallPlanet / 2 - dotSize * 2; 35 | const radius = maxRadius * Math.sqrt(Math.random()); 36 | 37 | // Cartesian coodinates. 38 | return { 39 | x: radius * Math.cos(angle) + smallPlanet / 2, 40 | y: radius * Math.sin(angle) + smallPlanet / 2 41 | }; 42 | }); 43 | 44 | const myCustomTransition = transitionInfo => { 45 | const { progress, start, end } = transitionInfo; 46 | const opacity = progress.interpolate({ 47 | inputRange: [0, start, end, 1], 48 | outputRange: [1, 1, 0, 0] 49 | }); 50 | const translateX = progress.interpolate({ 51 | inputRange: [0, start, end, 1], 52 | outputRange: [0, 0, 100, 100] 53 | }); 54 | 55 | return { opacity, transform: [{ translateX }] }; 56 | }; 57 | 58 | function cacheImages(images) { 59 | return images.map(image => { 60 | if (typeof image === 'string') { 61 | return Image.prefetch(image); 62 | } else { 63 | return Asset.fromModule(image).downloadAsync(); 64 | } 65 | }); 66 | } 67 | 68 | const Screen1 = ({ navigation }) => { 69 | return ( 70 | 71 | 78 | 79 | 80 | 81 | 82 | 83 | navigation.navigate('Screen2')} style={{ position: 'absolute', bottom: 70 }}> 84 | 85 | 86 | 87 | 88 | ); 89 | }; 90 | 91 | const Screen2 = ({ navigation }) => { 92 | return ( 93 | 94 | 95 | 96 | {}} /> 97 | 98 | 99 | {coordinates.map(({ x, y }, i) => { 100 | return ( 101 | navigation.navigate('Screen3', { coordinates: { x, y } })} 103 | hitSlop={{ top: 20, left: 20, bottom: 20, right: 20 }} 104 | style={{ position: 'absolute' }} 105 | key={i} 106 | > 107 | 126 | 127 | ); 128 | })} 129 | 130 | 131 | 132 | { 134 | navigation.navigate('Screen1'); 135 | }} 136 | > 137 | 138 | 139 | 140 | 141 | 142 | {timings.map((time, i) => { 143 | return ( 144 | 152 | {time}m 153 | 154 | ); 155 | })} 156 | 157 | 158 | ); 159 | }; 160 | 161 | const Screen3 = ({ navigation }) => { 162 | const { x, y } = navigation.state.params.coordinates; 163 | return ( 164 | 165 | 171 | 172 | 173 | 174 | 175 | 188 | 189 | 190 | navigation.navigate('Screen2')}> 191 | 192 | 193 | 194 | 195 | 196 | ); 197 | }; 198 | 199 | const AppContainer = createAppContainer( 200 | createFluidNavigator( 201 | { 202 | Screen1: { 203 | screen: Screen1 204 | }, 205 | Screen2: { 206 | screen: Screen2 207 | }, 208 | Screen3: { 209 | screen: Screen3 210 | } 211 | }, 212 | { 213 | headerMode: 'none', 214 | initialRouteName: 'Screen1' 215 | } 216 | ) 217 | ); 218 | 219 | export default class App extends React.Component { 220 | state = { 221 | isReady: false 222 | }; 223 | 224 | async _loadAssetsAsync() { 225 | const imageAssets = cacheImages([require('./assets/mars_planet.png')]); 226 | 227 | await Promise.all([...imageAssets]); 228 | } 229 | 230 | componentWillMount() { 231 | StatusBar.setHidden(true); 232 | } 233 | 234 | render() { 235 | if (!this.state.isReady) { 236 | return ( 237 | this.setState({ isReady: true })} 240 | onError={console.warn} 241 | /> 242 | ); 243 | } 244 | return ( 245 | 246 | 247 | 248 | ); 249 | } 250 | } 251 | 252 | const styles = StyleSheet.create({ 253 | container: { 254 | flex: 1, 255 | alignItems: 'center', 256 | justifyContent: 'center', 257 | backgroundColor: '#000' 258 | }, 259 | marsPlanetScreen1: { 260 | width: height, 261 | height: height, 262 | resizeMode: 'contain', 263 | position: 'absolute', 264 | bottom: -height 265 | }, 266 | marsPlanetScreen2: { 267 | width: smallPlanet, 268 | height: smallPlanet, 269 | resizeMode: 'contain', 270 | transform: [ 271 | { 272 | rotate: rotation 273 | } 274 | ] 275 | }, 276 | marsPlanetScreen3: { 277 | resizeMode: 'contain', 278 | width: bigPlanet, 279 | height: bigPlanet, 280 | transform: [{ rotate: rotation }] 281 | }, 282 | headerNavigation: { 283 | position: 'absolute', 284 | top: 0, 285 | left: 0, 286 | width, 287 | alignItems: 'flex-start', 288 | justifyContent: 'center', 289 | padding: 20 290 | }, 291 | 292 | timingScrollViewContainer: { position: 'absolute', bottom: 0, left: 0 }, 293 | timingBox: { 294 | marginRight: 20, 295 | borderWidth: 1, 296 | borderColor: '#fff', 297 | borderRadius: 10, 298 | padding: 10, 299 | height: 120, 300 | width: 110, 301 | alignItems: 'flex-end', 302 | justifyContent: 'flex-end' 303 | }, 304 | timingBoxText: { 305 | fontSize: 24, 306 | color: 'white', 307 | fontFamily: Platform.select({ ios: 'Menlo', android: 'monospace' }), 308 | fontWeight: 'bold' 309 | }, 310 | dotStyle: { 311 | width: dotSize, 312 | height: dotSize, 313 | borderRadius: dotSize, 314 | backgroundColor: '#fff', 315 | position: 'absolute' 316 | } 317 | }); 318 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Would you like to support me? 2 | 3 | Buy Me A Coffee 4 | 5 | - Paypal: https://paypal.me/catalinmiron 6 | 7 | # React Native Shared Element Transition 8 | 9 | # Run on your device 10 | 11 | Snack: https://snack.expo.io/@catalinmiron/react-native-shared-element-transition 12 | 13 | ### Youtube tutorial 14 | 15 | [![React Native Shared Element Transition Youtube tutorial](react-native-shared-element-transition.png)](https://youtu.be/42BkfJ-h54E) 16 | 17 | In this lesson we’re going to be building a **Shared Element Transition** in React Native using react-navigation, react-navigation-fluid-transition, React Native and Expo for creating the react-native project. 18 | 19 | - Expo: https://expo.io/ 20 | - React Navigation: https://reactnavigation.org 21 | - React Navigation Fluid Transition: https://github.com/fram-x/FluidTransitions 22 | - Inspiration: https://dribbble.com/shots/6520262-Mars 23 | - Planet image: https://pngimg.com/download/61156 24 | 25 | You can find me on: 26 | 27 | - Github: http://github.com/catalinmiron 28 | - Twitter: http://twitter.com/mironcatalin 29 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "react-native-shared-element-transition", 4 | "slug": "react-native-shared-element-transition", 5 | "privacy": "public", 6 | "sdkVersion": "34.0.0", 7 | "platforms": [ 8 | "ios", 9 | "android", 10 | "web" 11 | ], 12 | "version": "1.0.0", 13 | "orientation": "portrait", 14 | "icon": "./assets/icon.png", 15 | "splash": { 16 | "image": "./assets/splash.png", 17 | "resizeMode": "contain", 18 | "backgroundColor": "#ffffff" 19 | }, 20 | "updates": { 21 | "fallbackToCacheTimeout": 0 22 | }, 23 | "assetBundlePatterns": [ 24 | "**/*" 25 | ], 26 | "ios": { 27 | "supportsTablet": true 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catalinmiron/react-native-shared-element-transition/b07fd26484e7ca1f37a6e9d6b1e2b7d7d7ecd08b/assets/icon.png -------------------------------------------------------------------------------- /assets/mars_planet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catalinmiron/react-native-shared-element-transition/b07fd26484e7ca1f37a6e9d6b1e2b7d7d7ecd08b/assets/mars_planet.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catalinmiron/react-native-shared-element-transition/b07fd26484e7ca1f37a6e9d6b1e2b7d7d7ecd08b/assets/splash.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "scripts": { 4 | "start": "expo start", 5 | "android": "expo start --android", 6 | "ios": "expo start --ios", 7 | "web": "expo start --web", 8 | "eject": "expo eject" 9 | }, 10 | "dependencies": { 11 | "@expo/vector-icons": "^10.0.3", 12 | "expo": "^34.0.1", 13 | "expo-asset": "^6.0.0", 14 | "react": "16.8.3", 15 | "react-dom": "^16.8.6", 16 | "react-native": "https://github.com/expo/react-native/archive/sdk-34.0.0.tar.gz", 17 | "react-native-animatable": "^1.3.2", 18 | "react-native-gesture-handler": "^1.3.0", 19 | "react-native-web": "^0.11.4", 20 | "react-navigation": "^3.11.1", 21 | "react-navigation-fluid-transitions": "^0.3.2" 22 | }, 23 | "devDependencies": { 24 | "babel-preset-expo": "^6.0.0" 25 | }, 26 | "private": true 27 | } 28 | -------------------------------------------------------------------------------- /react-native-shared-element-transition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catalinmiron/react-native-shared-element-transition/b07fd26484e7ca1f37a6e9d6b1e2b7d7d7ecd08b/react-native-shared-element-transition.png --------------------------------------------------------------------------------