A tiny parallax ScrollView for React Native — header parallax, pull-to-zoom, depth layers, scroll-aware overlays and a seamless bounce. All on the UI thread.
Every screen ships in the example app — lift them straight into a project.
Video hero, shrinking title, crossfading navbars
Cover photo, fade-in navbar, pull-to-zoom
Every cover element on its own depth layer
Nine-layer SVG mountain scene
Three tiny files. No theme provider, no config, no native code.
The header eases away slower than the body as you scroll — set the speed with a single parallaxFactor.
Give the header a fixed height and it zooms in on over-scroll, top edge pinned, bottom edge glued to the body.
Stack ParallaxLayers at different speeds to fake depth — skies drift, foregrounds rush.
An overlay slot above the scroll content that reads the scroll offset — fade-in navbars in a few lines.
Rounded body corners show the header's image behind them (bodyOverlap), not the screen background.
The bottom over-scroll region is painted in the body's color (bounceColor) — the bounce stays, the flash goes.
bodyStyle and headerStyle accept Reanimated animated styles — morph the body as it docks.
Built on react-native-reanimated worklets. Works on the New Architecture.
react-native-reanimated (≥ 3) is a peer dependency —
make sure its Babel plugin is set up (Expo SDK 50+ wires it automatically).
import { ParallaxScrollView } from 'react-native-parallax-flow'; export default function Screen() { return ( <ParallaxScrollView headerHeight={280} parallaxFactor={0.5} headerStyle={{ backgroundColor: '#0f766e' }} bodyStyle={{ backgroundColor: '#fff', borderTopLeftRadius: 28, borderTopRightRadius: 28, }} header={<Hero />} > <YourContent /> </ParallaxScrollView> ); }
| Prop | Type / default | Description |
|---|---|---|
| header | ReactNode | Content of the parallax header region. Can be null. |
| children | ReactNode | The body that slides up over the header. |
| headerHeight | number · auto | Fixed header height. Omit to size to content. A fixed height enables pull-to-zoom. |
| parallaxFactor | number · 0.5 | Fraction of scroll speed the header moves at, clamped to [0, 1]. 1 = keeps pace, 0 = frozen. |
| headerParallax | boolean · true | Set false so each ParallaxLayer owns its own motion in multi-layer scenes. |
| headerStyle | AnimatedStyle | Style for the header container. Accepts Reanimated animated styles. |
| bodyStyle | AnimatedStyle | Style for the body wrapper — background, radius, shadow. Accepts animated styles. |
| overlay | ReactNode | Rendered absolutely above the scroll content, inside the parallax context — children can call useParallaxScroll(). Never blocks scrolling. |
| scrollY | SharedValue<number> | External shared value mirroring the scroll offset, for screen-level scroll animations. |
| bodyOverlap | number · from radius | How many px the body overlaps the header, so rounded corners reveal the header content. Defaults to the body's top corner radius. |
| bounceColor | ColorValue · from bodyStyle | Color painted under the body for the bottom over-scroll region — keeps the iOS bounce seamless. |
| contentContainerStyle | ViewStyle | Merged into the ScrollView's contentContainerStyle. |
| scrollViewProps | ScrollViewProps | Any other ScrollView props. onScroll / scrollEventThrottle / contentContainerStyle are managed internally. |
| ref | Animated.ScrollView | Forwarded to the inner ScrollView — scrollTo, scrollToEnd, etc. |
| Prop | Type / default | Description |
|---|---|---|
| factor | number · 0.5 | Fraction of scroll speed, [0, 1]. Lower = slower = reads as further away. |
| zoomOnPull | boolean · false | Scale the layer up on top over-scroll — best on the nearest layers. |
| zoomReference | number · 300 | Height the zoom ramp is normalized against — usually the header height. |
| style | ViewStyle | Extra style on top of the absolute-fill default. |
| pointerEvents | · 'none' | Layers don't eat touches by default. |
Returns the shared scroll offset (SharedValue<number>).
Available anywhere inside the component — header, overlay, body — for building custom scroll-linked effects.
The classic "title bar appears once the hero scrolls away". Render the bar through overlay so it can read the offset:
function FadeInBar({ start, end, children }) { const scrollY = useParallaxScroll(); const style = useAnimatedStyle(() => { const t = interpolate(scrollY.value, [start, end], [0, 1], Extrapolation.CLAMP); return { opacity: t, transform: [{ translateY: (1 - t) * -10 }] }; }); return <Animated.View style={style}>{children}</Animated.View>; } <ParallaxScrollView header={<Hero />} overlay={<FadeInBar start={110} end={200}>…</FadeInBar>}> <Body /> </ParallaxScrollView>
Pass your own shared value through scrollY and morph the body while it scrolls — here the top radius flattens as it docks under the navbar:
const scrollY = useSharedValue(0); const dockStyle = useAnimatedStyle(() => { const radius = interpolate(scrollY.value, [180, 280], [28, 0], Extrapolation.CLAMP); return { borderTopLeftRadius: radius, borderTopRightRadius: radius }; }); <ParallaxScrollView headerHeight={380} scrollY={scrollY} bodyStyle={[{ backgroundColor: '#fff' }, dockStyle]} header={<Hero />}> <Body /> </ParallaxScrollView>
<ParallaxScrollView headerHeight={600} headerParallax={false} header={ <> <ParallaxLayer factor={0.05}>{/* sky — barely moves */}</ParallaxLayer> <ParallaxLayer factor={0.3}>{/* mountains */}</ParallaxLayer> <ParallaxLayer factor={0.9} zoomOnPull zoomReference={600}> {/* foreground — nearest, zooms on pull */} </ParallaxLayer> </> }> <Body /> </ParallaxScrollView>
bodyOverlap px sit under the body.bounceColor; pass it explicitly for animated or transparent bodies.