Skip to content
Native Animation
Route Elevation Scroll animation preview

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

npx nativeanimation add route-elevation-scroll
Skia

Route Elevation Scroll

A trail story with a scroll-linked elevation profile and summit flags, with configurable chapters, elevation samples, accent color, and fonts.

$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 trail story with a scroll-linked elevation profile and summit flags, with configurable chapters, elevation samples, accent color, and fonts.

Why this one

Elevation tells a mountain route's story more clearly than a line on a map. The active slope changes from green to amber to red as the climb steepens. Each day briefly magnifies the profile, then restores the complete route before the next chapter.

How it works

  1. onLayout measures the container. The scroll handler converts scroll offset into one continuous chapter progress on the UI thread.
  2. Smoothstep interpolation holds poses within 20% of each chapter boundary. Distance, altitude, cumulative ascent, and the trail point derive from that progress.
  3. Skia draws the elevation area, clips the traveled fill to the current distance, and colors the current slope from its calculated grade.
  4. A spring scales the profile horizontally during each day's climb. Summit flags plant when the point reaches their distance; metric lighting responds to proximity.
  5. Photos enter above the previous photo. Headline entrance and exit windows never overlap. Native scroll snaps to chapters; web scroll remains continuous.
  6. The demo clock calls scrollToProgress at about 2.6 seconds per chapter using the editorial easing curve. A black fade hides its reset. Touching or dragging stops autoplay.

Install

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

Use SDK-compatible versions. Configure Reanimated and worklets in the host application. On web, load CanvasKit before importing this component with Skia's LoadSkiaWeb / WithSkiaWeb integration or the host's equivalent lazy loader. Keep local images in your application and load custom fonts before mounting. Icons: Lucide (ISC).

Usage

import { RouteElevationScroll } from './route-elevation-scroll';

const photo = require('./mountains.jpg');
const chapters = [
  { name: 'Trailhead', title: 'Start the climb.', emphasis: 'Find your pace.', body: 'Walk 6 miles toward the first pass.', image: photo, color: '#F8BB77' },
  { name: 'First pass', title: 'Above the trees.', emphasis: 'Take in the view.', body: 'Reach 7,400 ft before descending.', image: photo, color: '#F97316' },
  { name: 'Ridgeline', title: 'Follow the ridge.', emphasis: 'Keep looking up.', body: 'Cross 8 miles of open trail.', image: photo, color: '#F6AC6C' },
  { name: 'Camp', title: 'You made it.', emphasis: 'Rest under the stars.', body: 'Finish 26 miles at mountain camp.', image: photo, color: '#FFCE8D' },
];
const samples = [
  { miles: 0, feet: 3200 }, { miles: 8, feet: 7400 },
  { miles: 12, feet: 4800 }, { miles: 17, feet: 8200 },
  { miles: 20, feet: 5600 }, { miles: 24, feet: 9000 },
  { miles: 26, feet: 8800 },
];

<RouteElevationScroll chapters={chapters} samples={samples} />;

// Customize the material and motion without changing the scroll recipe.
<RouteElevationScroll
  chapters={chapters}
  samples={samples}
  accentColor="#F97316"
  zoomScale={1.6}
  snapToChapters={false}
  passes={[{ name: 'First pass', miles: 8, feet: 7400 }]}
  ctaLabel="Reserve a place"
  onCtaPress={() => openReservation()}
/>;

The parent must provide bounded height. Supply four chapters, at least two increasing samples, a positive final distance, and opaque six-digit hex accent colors. Align pass distances and elevations with the samples.

Props

PropTypeDefaultDescription
chaptersreadonly RouteChapter[]requiredFour photos, names, headlines, colors, and descriptions.
samplesreadonly ElevationSample[]requiredIncreasing distances in miles and elevations in feet.
passesreadonly RoutePass[][]Names and positions for summit flags.
brandReactNodenoneOptional top-left brand lockup.
routeNamestringMountain routeRoute title below the lockup.
accentColorstring#F97316Trail fill, CTA, and summit color.
ctaLabelstringExplore this routeFinal action label.
onCtaPress() => voidnoneFinal action callback.
onChapterChange(index: number) => voidnoneNearest chapter, reported on the JS thread.
onInteraction() => voidnoneTouch or drag callback for stopping demo autoplay.
fontFamilystringsystemInterface font.
titleFontFamilystringsystemEditorial font.
italicFontFamilystringsystemItalic title font.
monoFontFamilystringsystemRolling figure font.
zoomScalenumber1.85Horizontal zoom strength.
snapToChaptersbooleantrueNative chapter snapping.
styleStyleProp<ViewStyle>noneRoot container style.
testIDstringnoneRoot test ID.

Ref methods: scrollToProgress(progress, animated = false) scrolls to a continuous chapter position; fade(black) transitions the scene to or from black over 200 ms. Autoplay belongs to the demo, not the reusable component.

Compatibility

Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, worklets 0.13.0, Skia 2.14.0. Dependencies are recorded from imports in piece.json. Intended for Expo Go with these SDK-bundled modules. Android uses BlurTargetView and dimezisBlurViewSdk31Plus, with a translucent fallback on older versions. Supports iOS, Android, and web.

Accessibility

Each chapter has a header with a full spoken day description. The final CTA is a labeled button in the scroll content, with a minimum 52 pt height. Four 44 pt chapter buttons provide a tap alternative to scrolling. Reduced motion removes zoom, translations, flag bounce, press scaling, and rolling autoplay progression; photos and chapter titles use 200 ms fades. There are no parallax or velocity trails.

Customization notes

The route follows scroll position; the imperative scroll API controls programmatic pacing.