Skip to content
Native Animation
Journey Map Scroll animation preview

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

npx nativeanimation add journey-map-scroll
Skia

Journey Map Scroll

A scroll-driven itinerary with route-following camera motion and pop-out postcards, with configurable stops, copy, 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 scroll-driven itinerary with route-following camera motion and pop-out postcards, with configurable stops, copy, fonts, and accent color.

Why this one

The map travels with you instead of sitting above a list. Each postcard originates at its own pin. An original dotted sea, organic coast, mountain contours, and a luminous route tip give the itinerary its own visual language. No map service, tiles, or location data are needed.

How it works

  1. An animated ScrollView converts its measured scroll offset into one continuous chapter progress, p, from 0 to 3.
  2. Smoothstep poses hold the traveler still within 20% of each chapter. Cubic curves and length-normalized samples keep the traveler aligned with the trimmed route.
  3. The camera looks 15% ahead along the route and follows with interruptible springs. All map geometry is in an independent 600-unit coordinate system.
  4. Postcards launch from the transformed pin into their lower-screen position. Headline windows finish exiting before the next begins entering.
  5. Incoming background photos fade over the previous photo. Top and bottom veils maintain contrast. Fast-scroll route light activates above 600 points per second; the intro also shows the route light.
  6. The cover title, brand name, and stop counter use ordinary views and percentage positioning, independently of map measurement. The dotted sea is built only across the visible viewport; the land closes outside all camera poses, and the map window clips to rounded lower corners.
  7. The demo drives actual scrolling through the imperative ref, stops on interaction, and resets behind a short black fade. The final action lives inside the scroll content.

Install

npx expo install @shopify/react-native-skia expo-blur expo-image expo-linear-gradient react-native-reanimated react-native-worklets

Configure Reanimated/worklets as required by your Expo project. On web, initialize CanvasKit and load this component lazily after Skia is ready, using your app's Skia web bootstrap. Keep your loading fallback outside the Skia gate if the cover must appear before CanvasKit initializes. This catalog currently gates the entire demo in App.tsx, so its cold-load fallback cannot be replaced from this component folder. Bundle images locally to avoid an empty postcard while a network image loads. Icons: Lucide (ISC); paths are included in icons.ts.

Usage

import { JourneyMapScroll, type JourneyStop } from './journey-map-scroll';

// Supply four original stops, each with its own local image.
const stops: readonly [JourneyStop, JourneyStop, JourneyStop, JourneyStop] = [
  harborStop, cliffStop, valleyStop, mountainStop,
];

<JourneyMapScroll stops={stops} onAction={() => saveTrip()} />;
const journey = useRef<JourneyMapScrollRef>(null);

<JourneyMapScroll
  ref={journey}
  stops={stops}
  title="Coast to Peaks"
  cameraScale={1.6}
  accentColor="#38BDF8"
  fontFamily="Geist_400Regular"
  serifFontFamily="InstrumentSerif_400Regular"
  italicFontFamily="InstrumentSerif_400Regular_Italic"
  monoFontFamily="GeistMono_400Regular"
  onInteraction={stopAutoplay}
  onIndexChange={setSelectedStop}
/>;

// Fractional chapters work too. Autoplay belongs in the caller.
journey.current?.goTo(2, 1900);

Props

PropTypeDefaultDescription
stopsFour-element readonly JourneyStop tupleRequiredNames, day ranges, two-line headlines, descriptions, local images, miles, weather, lodging, and accents.
brandReactNodeNoneOptional brand lockup; demo-only brand stays in the demo.
titlestringJourneyItinerary title.
summarystringFour stopsCompact itinerary facts.
mapLabelstringROUTE OVERVIEWShort map caption.
actionTitlestringKeep your journey.Final section heading.
actionDescriptionstringSave all four stops in one itinerary.Final section description.
fontFamilystringSystemInterface font.
serifFontFamilystringSystemMain headline font.
italicFontFamilystringSystemAccent headline font.
monoFontFamilystringSystemCounter font.
accentColorstring#38BDF8Route and traveler ring accent.
backgroundColorstring#071C2BRoot and veil color.
cameraScalenumber1.6Map size relative to its window.
snapToChaptersbooleantrueChapter snapping on native only.
actionLabelstringSave itineraryFinal button text and accessible label.
onAction() => voidNoneFinal button callback.
onIndexChange(index: number) => voidNoneNearest chapter callback on the JS thread.
onInteraction() => voidNoneTouch, wheel, or drag callback for canceling autoplay.
styleStyleProp<ViewStyle>NoneRoot styling. Root defaults to flex: 1.
testIDstringNoneRoot test identifier.
durationnumber300Reset curtain fade duration in milliseconds. Spring physics and reduced-motion timing are unchanged.
stopsreadonly [ JourneyStop, JourneyStop, JourneyStop, JourneyStop, ]RequiredExactly four stops in itinerary order.
brandReactNodeundefinedOptional brand lockup rendered above the map.
titlestring'Journey'Itinerary title.
summarystring'Four stops'Compact itinerary facts.
mapLabelstring'ROUTE OVERVIEW'Caption under the map.
actionTitlestring'Keep your journey.'Final section heading.
actionDescriptionstring'Save all four stops in one itinerary.'Final section description.
fontFamilystringundefinedInterface font; system font when omitted.
serifFontFamilystringundefinedEditorial headline font.
italicFontFamilystringundefinedItalic headline font.
monoFontFamilystringundefinedCounter font.
accentColorstring'#38BDF8'Route highlight.
backgroundColorstring'#071C2B'Root surface color.
cameraScalenumber1.6Size of the map relative to its viewport.
snapToChaptersbooleantrueNative chapter snapping. Web always scrolls freely.
actionLabelstring'Save itinerary'Final action label.
onAction() => voidundefinedCalled when the final action is pressed.
onIndexChange( index: number, ) => voidundefinedCalled when the nearest chapter changes.
onInteraction() => voidundefinedCalled on touch, wheel, or drag so callers can stop autoplay.
styleStyleProp<ViewStyle>undefinedOuter container style.
testIDstringundefinedRoot test identifier.

Ref methods: goTo(chapter, duration = 1900), replay(), and reset(). Font assets must be loaded by the caller. The component never imports the catalog theme or demo assets.

Compatibility

Targets Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, worklets 0.13.0, and Skia 2.14.0. Uses Expo Go's installed modules; no new native dependency is introduced. Supports iOS, Android, and web. The demo entry compiles into iOS and Android development bundles (HTTP 200). The photograph uses 20% of container height on taller phones and contracts on compact phones to preserve the two-line headline and data row.

Accessibility

Each chapter includes a descriptive accessible block and a header-role headline. Inactive postcards are hidden from assistive technology. Next-stop buttons provide a tap alternative to scrolling, with 44-point minimum targets. The final button exposes its role and label and scales to 0.97 on press. Reduced motion removes camera travel/zoom, postcard flight, pulse, ambient drift, and trails; chapter arrivals use 200 ms fades. Web scrolling never uses mandatory snap.