Skip to content
Native Animation
Pinned Horizontal Scroll animation preview

This preview uses fictional demo content. See the props below for the customization options available in this component.

npx nativeanimation add pinned-horizontal-scroll

Pinned Horizontal Scroll

A pinned gallery that turns vertical scroll into horizontal photo travel, with configurable chapters, intro content, fonts, and accent color.

$9 for this animation

Add to your project

Copy the component and its assets into your app with the Native Animation CLI, or paste the agent prompt into Claude Code, Codex or Cursor once the MCP server is connected.

About this animation

A pinned gallery that turns vertical scroll into horizontal photo travel, with configurable chapters, intro content, fonts, and accent color.

Why this one

One familiar vertical gesture controls the entire itinerary. Interior image parallax moves 18% slower than each card, while oversized chapter numbers, resting poses, and layered photographic backdrops give each stop its own composition. There is no separate horizontal swipe to discover.

How it works

  1. Measure the component with onLayout; intro and gallery viewport follow its height.
  2. Reserve a vertical pin distance of chapterCount × 0.9 × height, plus one viewport for the pinned scene.
  3. Read vertical offset on the UI thread with useAnimatedScrollHandler. Derive one continuous chapter progress from it.
  4. Render the gallery as an absolute sibling of the scroll view, above a spacer that reserves its scroll distance. The scene enters from below, stays at a constant vertical position throughout the pin interval, and exits upward. Map progress into a horizontal rail, with a 20% resting window around each chapter and smoothstep transitions.
  5. Offset oversize images inside clipped cards; derive scale, perspective, darkening, headline visibility, and progress from the same chapter value. Backdrop photos fade in above the preceding layer.
  6. Release the scene at the end. Render the closing CTA inside the scrolling content. Native chapter snapping is disabled on web so mouse-wheel scrolling remains continuous.

Install

npx expo install expo-blur@~58.0.5 expo-image@~58.0.17 expo-linear-gradient@~58.0.3 react-native-reanimated@4.7.1 react-native-worklets@0.13.0

Copy this folder without demo/. No shared theme, remote assets, Skia setup, or custom native module is required. Icons: Lucide (ISC); their copied paths are rendered as SVG image sources.

Usage

import { PinnedHorizontalScroll } from './pinned-horizontal-scroll';

const chapters = [
  {
    id: 'coast', image: require('./coast.jpg'), title: 'Walk the coast.',
    subtitle: 'Take your time.', eyebrow: 'THE COAST',
    description: 'Follow 3 miles of shoreline.', days: 'Day 1–2',
  },
];

<PinnedHorizontalScroll chapters={chapters} />;

A customized composition:

<PinnedHorizontalScroll
  chapters={chapters}
  intro={<Intro />}
  closing={<BookingSection />}
  accentColor="#0F766E"
  cardWidthRatio={0.78}
  cardHeightRatio={0.58}
  parallaxIntensity={0.18}
  fontFamily="YourInterfaceFont"
  serifFontFamily="YourEditorialFont"
  italicFontFamily="YourItalicFont"
  monoFontFamily="YourCounterFont"
  onIndexChange={(index) => trackStop(index)}
/>

Props

PropTypeDefaultPurpose
chaptersreadonly CoastChapter[]requiredOrdered images, two-line titles, categories, details, day ranges, and optional accents
introReactNodeundefinedNormal first viewport
closingReactNodeundefinedNormal closing section; put the CTA here
fontFamilystringsystemInterface text
serifFontFamilystringsystemFirst title line
italicFontFamilystringsystemSecond title line
monoFontFamilystringsystemLarge numbers and chapter counter
accentColorstring#0F766EProgress highlight
colorSchemelight / dark / autolightContainer surface theme; supplied slots own their colors
cardWidthRationumber0.78Width fraction, clamped to 0.6–0.9
cardHeightRationumber0.58Height fraction, clamped to 0.4–0.64
parallaxIntensitynumber0.18Interior lag, clamped to 0–0.3
snapToChaptersbooleantrueNative snapping only
onIndexChange(index: number) => voidundefinedNearest chapter callback on JS
onInteraction() => voidundefinedTouch or manual scroll; stop caller autoplay
styleStyleProp<ViewStyle>undefinedRoot container style
testIDstringundefinedTest identifier
durationnumber300Reset curtain fade duration in milliseconds. Spring physics and reduced-motion timing are unchanged.
chaptersreadonly CoastChapter[]RequiredOrdered chapters; an empty list leaves intro and closing content usable.
introReactNodeundefinedNormal scrolling introduction.
closingReactNodeundefinedNormal scrolling closing section, including any CTA.
fontFamilystringundefinedInterface font; defaults to the system font.
serifFontFamilystringundefinedEditorial heading font.
italicFontFamilystringundefinedItalic editorial font.
monoFontFamilystringundefinedTabular counter font.
accentColorstring'#0F766E'Highlight color. Defaults to teal.
colorScheme'light' | 'dark' | 'auto''light'Surface theme. Defaults to light.
cardWidthRationumber0.78Card width as a fraction of measured width. Defaults to 0.78.
cardHeightRationumber0.58Card height as a fraction of measured height. Defaults to 0.58.
parallaxIntensitynumber0.18Interior image lag. Defaults to 0.18.
snapToChaptersbooleantrueSnap on native platforms only. Defaults to true.
onIndexChange(index: number) => voidundefinedCalled when the nearest chapter changes.
onInteraction() => voidundefinedCalled on touch or manual scroll, so the caller can stop autoplay.
styleStyleProp<ViewStyle>undefinedContainer style.
testIDstringundefinedTesting identifier.
followLabelstring'SCROLL TO FOLLOW THE COAST'Label for the chapter scroll hint.
exploreLabelstring'SCROLL TO EXPLORE'Label for the opening scroll hint.

The ref exposes goTo(chapter, duration = 2600), stop(), and reset(). Use -1 for the intro and chapters.length for closing. Reset fades to black, returns to the top, and reveals the introduction. Autoplay belongs to the demo and stops on touch or drag; chapter buttons remain usable after stopping it.

Compatibility

Targets Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, and Worklets 0.13.0. Compatible with Expo Go's bundled modules. Android glass uses BlurTargetView and the SDK 31+ blur method; older Android versions retain a translucent pill. Supports iOS, Android, and web.

Accessibility

Each chapter title has the header role. Chapter markers and the first-screen scroll hint are buttons with labels and hit areas of at least 44 points, providing a tap alternative to scrolling. The final demo CTA has a descriptive label and live confirmation. Reduced motion removes interior parallax, zoom, perspective, horizontal card travel, and hint bobbing; chapters fade over 200 ms. The gallery stays outside the scrolling content in both motion modes; only its entry and exit move vertically. Noninteractive gallery layers let scroll gestures reach the underlying scroll view, while chapter markers remain tappable.

Demo timing

The headline and first photographic preview enter in 850 ms. At 700 ms autoplay starts a 1,700 ms scroll into the first pinned card. Further chapters take 2,600 ms with (0.77, 0, 0.175, 1), followed by the closing section and a black reset. The CTA compresses to 0.97 on press and shows a local request confirmation.