I stumbled on this animation a couple of weeks ago on Twitter. It was a beautiful prototype from @JordanAmblin.
View the original prototype on X →
So I decided to recreate it in React Native using react-native-reanimated, react-native-gesture-handler, and @gorhom/bottom-sheet.
The goal was to utilize actual components to ensure it can be easily adapted for real-world applications.
Demo
Videos from the original post. iOS and Android behave identically with the Reanimated worklet driving the underlay.
Approach
- Gesture:
Panfromreact-native-gesture-handlermapped to a shared value. No JS thread hops during drag. - Animation:
useAnimatedStyle+withSpringfor the sheet and the underlay scale/opacity.interpolateties them together. - Sheet:
@gorhom/bottom-sheetfor snap points and backdrop. Underlay is a sibling view that reacts to the sheet's animated position. - Detail:
simultaneousWithExternalGestureso the sheet doesn't fight scroll inside.
import BottomSheet from "@gorhom/bottom-sheet";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import Animated, {
useSharedValue,
useAnimatedStyle,
interpolate,
withSpring,
} from "react-native-reanimated";
const position = useSharedValue(0);
const underlayStyle = useAnimatedStyle(() => ({
opacity: interpolate(position.value, [0, 1], [0, 1]),
transform: [{ scale: interpolate(position.value, [0, 1], [0.96, 1]) }],
}));
Takeaway
Prototypes are cheap. Making them shippable means real components, real gestures, and worklets that stay on the UI thread.
Full example: Stringsaaed/underlay-pattern