Last active
August 5, 2023 15:47
-
-
Save cristiangu/74ba83cd4ea243cda92f5b84a6a2c889 to your computer and use it in GitHub Desktop.
React Native Animated Bottom Modal in a Native Stack Navigator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<RootStack.Navigator> | |
<RootStack.Screen name="other-screen" component={OtherScreen} /> | |
<RootStack.Group | |
screenOptions={{ | |
presentation: 'containedTransparentModal', | |
animation: 'slide_from_bottom', | |
}}> | |
<RootStack.Screen name="g-modal" component={GModal} /> | |
</RootStack.Group> | |
</RootStack.Navigator> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// [...] | |
return ( | |
<Pressable onPress={closeModal} style={{ flex: 1 }}> | |
<View | |
style={{ | |
flex: 1, | |
backgroundColor: 'transparent', | |
justifyContent: 'flex-end', | |
}}> | |
<Pressable> | |
{*/ This 👆 Pressable will take in all the touch events happening inside. */} | |
<PanGestureHandler | |
onEnded={onPanGestureEnd} | |
onGestureEvent={onPanGestureEvent}> | |
{*/ [...] */} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useCallback } from 'react'; | |
import { Animated, Pressable, View } from 'react-native'; | |
import { | |
HandlerStateChangeEvent, | |
PanGestureHandler, | |
} from 'react-native-gesture-handler'; | |
const GBottomInfiniteView = React.memo(({ translateY }: any) => { | |
return ( | |
<Animated.View | |
style={[ | |
{ | |
zIndex: 0, | |
position: 'absolute', | |
width: '100%', | |
height: 1000, | |
backgroundColor: 'white', | |
}, | |
{ | |
transform: [ | |
{ | |
translateY: translateY.interpolate({ | |
inputRange: [0, 1000], | |
outputRange: [1000, 2000], | |
}), | |
}, | |
], | |
}, | |
]} | |
/> | |
); | |
}); | |
const GModal = ({ navigation }: any) => { | |
const translateY = React.useRef(new Animated.Value(0)).current; | |
const onPanGestureEvent = Animated.event( | |
[ | |
{ | |
nativeEvent: { | |
translationY: translateY, | |
}, | |
}, | |
], | |
{ useNativeDriver: true }, | |
); | |
const closeModal = useCallback(() => { | |
navigation.goBack(); | |
}, [navigation]); | |
const onPanGestureEnd = useCallback( | |
(event: HandlerStateChangeEvent) => { | |
// @ts-expect-error | |
if (event.nativeEvent.translationY > 150) { | |
closeModal(); | |
return; | |
} | |
Animated.spring(translateY, { | |
toValue: 0, | |
useNativeDriver: true, | |
friction: 5, | |
tension: 10, | |
}).start(); | |
}, | |
[closeModal, translateY], | |
); | |
const translateYTransform = { | |
transform: [ | |
{ | |
translateY: translateY, | |
}, | |
], | |
}; | |
return ( | |
<Pressable onPress={closeModal} style={{ flex: 1 }}> | |
<View | |
style={{ | |
flex: 1, | |
backgroundColor: 'transparent', | |
justifyContent: 'flex-end', | |
}}> | |
<Pressable> | |
<PanGestureHandler | |
onEnded={onPanGestureEnd} | |
onGestureEvent={onPanGestureEvent}> | |
<Animated.View | |
style={[ | |
{ | |
borderTopStartRadius: 20, | |
borderTopEndRadius: 20, | |
backgroundColor: 'white', | |
paddingTop: 8, | |
paddingHorizontal: 24, | |
justifyContent: 'center', | |
alignItems: 'center', | |
// ios shadow | |
shadowColor: '#101010', | |
shadowOffset: { width: 4, height: 8 }, | |
shadowOpacity: 0.25, | |
shadowRadius: 12, | |
// android shadow | |
elevation: 12, | |
}, | |
translateYTransform, | |
]}> | |
<View | |
style={{ width: '100%', height: 300, backgroundColor: 'red' }} | |
/> | |
</Animated.View> | |
</PanGestureHandler> | |
</Pressable> | |
<GBottomInfiniteView translateY={translateY} /> | |
</View> | |
</Pressable> | |
); | |
}; | |
const GModalMemo = React.memo(GModal); | |
export { GModalMemo as GModal }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useCallback } from 'react'; | |
import { Animated, View } from 'react-native'; | |
import { | |
HandlerStateChangeEvent, | |
PanGestureHandler, | |
} from 'react-native-gesture-handler'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
return ( | |
<View | |
style={{ | |
flex: 1, | |
backgroundColor: 'blue', | |
justifyContent: 'flex-end', | |
}}> | |
{/* | |
this view will cover the entire screen | |
when the modal is presented | |
*/} | |
</View> | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
return ( | |
<View | |
style={{ | |
flex: 1, | |
backgroundColor: "blue", | |
justifyContent: "flex-end", | |
}} | |
> | |
<PanGestureHandler> | |
<Animated.View | |
style={[ | |
{ | |
borderTopStartRadius: 20, | |
borderTopEndRadius: 20, | |
backgroundColor: "red", | |
paddingTop: 8, | |
paddingHorizontal: 24, | |
justifyContent: "center", | |
alignItems: "center", | |
}, | |
]} | |
> | |
<View | |
style={{ width: "100%", height: 300, backgroundColor: "orange" }} | |
/> | |
</Animated.View> | |
</PanGestureHandler> | |
</View> | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Gesture's Y axis value, will be used to animate our content view; up and downd. | |
const translateY = React.useRef(new Animated.Value(0)).current; | |
// This will be fired at least 60 times per second with an event containing | |
// gesture location data including the new Y axis value. | |
const onPanGestureEvent = Animated.event( | |
[ | |
{ | |
nativeEvent: { | |
translationY: translateY, | |
}, | |
}, | |
], | |
{ useNativeDriver: true } | |
); | |
const closeModal = useCallback(() => { | |
navigation.goBack(); | |
}, [navigation]); | |
// Called when the user lifted his finger from the screen. | |
const onPanGestureEnd = useCallback( | |
(event: HandlerStateChangeEvent) => { | |
// If the user swiped down more than 150 points, close the modal. | |
if (event.nativeEvent.translationY > 150) { | |
closeModal(); | |
return; | |
} | |
// This is optional, you can try to see how it looks without it. | |
Animated.spring(translateY, { | |
toValue: 0, | |
useNativeDriver: true, | |
friction: 5, | |
tension: 10, | |
}).start(); | |
}, | |
[closeModal, translateY] | |
); | |
return ( | |
<View | |
style={{ | |
flex: 1, | |
backgroundColor: "blue", | |
justifyContent: "flex-end", | |
}} | |
> | |
<PanGestureHandler | |
onEnded={onPanGestureEnd} | |
onGestureEvent={onPanGestureEvent} | |
> | |
<Animated.View | |
style={[ | |
{ | |
borderTopStartRadius: 20, | |
borderTopEndRadius: 20, | |
backgroundColor: "red", | |
paddingTop: 8, | |
paddingHorizontal: 24, | |
justifyContent: "center", | |
alignItems: "center", | |
}, | |
{ | |
transform: [ | |
{ | |
translateY: translateY, | |
}, | |
], | |
}, | |
]} | |
> | |
<View | |
style={{ width: "100%", height: 300, backgroundColor: "orange" }} | |
/> | |
</Animated.View> | |
</PanGestureHandler> | |
</View> | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const GBottomInfiniteView = React.memo(({ translateY }: any) => { | |
return ( | |
<Animated.View | |
style={[ | |
{ | |
zIndex: 0, | |
position: 'absolute', | |
width: '100%', | |
height: 1000, // it can be any big value | |
backgroundColor: 'purple', // change it to `white` after you saw it on your device | |
}, | |
{ | |
transform: [ | |
{ | |
translateY: translateY.interpolate({ | |
inputRange: [0, 1000], | |
outputRange: [1000, 2000], | |
}), | |
}, | |
], | |
}, | |
]} | |
/> | |
); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
return ( | |
{/* [...] */} | |
</PanGestureHandler> | |
<GBottomInfiniteView translateY={translateY} /> | |
</View> | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{/* [...] */} | |
<Animated.View | |
style={[ | |
{ | |
backgroundColor: 'white', | |
paddingTop: 8, | |
paddingHorizontal: 24, | |
justifyContent: 'center', | |
alignItems: 'center', | |
// ios shadow | |
shadowColor: '#101010', | |
shadowOffset: { width: 4, height: 8 }, | |
shadowOpacity: 0.25, | |
shadowRadius: 12, | |
// android shadow | |
elevation: 12, | |
}, | |
{ | |
transform: [ | |
{ | |
translateY: translateY, | |
}, | |
], | |
}, | |
]}> | |
<View | |
style={{ width: '100%', height: 300, backgroundColor: 'red' }} | |
/> | |
</Animated.View> | |
{/* [...] */} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Pressable } from 'react-native'; | |
// [...] | |
return ( | |
<Pressable onPress={closeModal} style={{ flex: 1 }}> | |
<View | |
style={{ | |
flex: 1, | |
backgroundColor: 'transparent', | |
justifyContent: 'flex-end', | |
}}> | |
<PanGestureHandler | |
{*/ [...] */} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment