Skip to content
Native Animation
Zigzag Reasons Scroll animation preview

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

npx nativeanimation add zigzag-reasons-scroll

Zigzag Reasons Scroll

A photo story with rolling reason numbers, drawn zigzags, and counting stats, with configurable sections, colors, fonts, and parallax strength.

$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 photo story with rolling reason numbers, drawn zigzags, and counting stats, with configurable sections, colors, fonts, and parallax strength.

Why this one

Large alternating numbers replace generic benefit icons. A thin mint route guides the eye between reasons while the landscape moves independently. The scene then opens into a turbine portrait and a concise four-line promise with live statistics. The route and the rolling typography give the story two distinct focal moments.

How it works

  1. A native animated ScrollView retains touch inertia, keyboard scrolling, and mouse-wheel behavior without mandatory snap points.
  2. One shared scroll offset controls chapter crossfades, slow background movement, zoom, and the connector lengths.
  3. Measured layouts trigger one reveal per viewport entry. Each text line fades from the opposite side with an offset in its reveal window.
  4. Numeric columns use static digit nodes. Each column advances only at its own carry; digits fade by distance and settle exactly on an integer. Suffixes have their own natural widths.
  5. Imperative section navigation animates a shared destination and calls Reanimated scrollTo on the UI thread. Touch cancels that animation immediately. The demo alone owns its timers.

Install

npx expo install expo-image expo-linear-gradient react-native-reanimated react-native-worklets

Use your project's Reanimated configuration and load any custom fonts before rendering. Icons: Lucide (ISC); copied paths are in icons.ts. No Skia setup is required.

Usage

import { ZigzagReasonsScroll } from './zigzag-reasons-scroll';

<ZigzagReasonsScroll
  sections={[
    {
      background: require('./landscape.jpg'),
      title: 'WHY MAKE THE SWITCH?',
      reasons: [{ number: 1, lines: ['A cleaner future.', 'A simpler choice.'] }],
    },
    {
      background: require('./detail.jpg'),
      metrics: [{ value: 48, suffix: 'K+', label: 'Homes powered' }],
    },
  ]}
/>
const story = useRef<ZigzagReasonsScrollRef>(null);

<ZigzagReasonsScroll
  ref={story}
  sections={sections}
  brand="Your brand"
  parallax={0.25}
  revealDuration={500}
  colors={{ accent: '#F4D890', text: '#FFF8EB', muted: '#E5D9C4' }}
  onMenuPress={openMenu}
  onScroll={offset => trackScroll(offset)}
/>
// Call from a host event handler:
story.current?.scrollToSection(1);

Props

PropTypeDefaultPurpose
sectionsZigzagSection[]requiredLocal photos, titles, reasons, metrics, initials
brand / brandMarkstring / ReactNodeempty / noneHost identity
fontFamilystringsystemBody font
titleFontFamilystringsystemHeadline font
monoFontFamilystringsystemReason numbers
metricFontFamilystringsystemStatistics
brandFontFamilystringsystemWordmark
colorsPartial paletteevergreen, ivory, mintBackground, text, muted, accent, veil, clear, control, border
parallaxnumber0.4Relative photo movement
revealDurationnumber600Reveal duration in milliseconds
menuLabel / nextLabelstringOpen menu / Next sectionControl labels
accessibilityLabelstringEnergy storyScroll region label
onMenuPress() => voidnoneHost menu; defaults to chapter advance
onScroll(offset: number) => voidnoneJS callback, quantized to 16-point changes
onInteraction() => voidnoneStop external autoplay on touch or manual scroll
style / testIDStyleProp / stringnoneContainer and stable control IDs

sections supports background, title, reasons: { number, lines }[], metrics: { value, suffix, label }[], and avatars: string[]. Values are nonnegative integers, capped at 999999. scrollToSection(index, duration = 650) clamps the zero-based index. Entry counters replay only after leaving the viewport.

Compatibility

Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, worklets 0.13.0. Compatible dependencies are included in Expo Go. Supports iOS, Android, and web.

The root fills its parent and measures with onLayout. Chapters grow beyond short containers instead of squeezing text; content width is capped at 440 points on tablets. Rotation recalculates measured chapter offsets. This is a full-screen presentation: the demo reserves 52 points above its controls; the host owns safe-area handling and may embed it inside a safe-area container. System font scaling remains enabled.

The supplied demo photographs are 768×1344. They are the commissioned assets and cannot guarantee 3× detail at full phone or tablet width, especially with zoom. Supply higher-resolution originals for production use.

Accessibility

Controls have 48-point targets, button roles, customizable labels, and 0.97 press feedback. The persistent next-section button is the tap alternative to scrolling. Headlines are headers; numeric columns expose one complete value instead of individual digits. Chapter changes announce the title or metric label. Reduced motion removes rolling digits, side entrances, the drawn route, zoom, and parallax; entry and navigation use 200 ms transitions. The demo stops on user interaction.

Demo timeline

The first reason rolls immediately (600 ms). At 0.9 seconds the demo starts a 3.7-second trip toward the turbine, then another 3.7-second trip toward the final landscape. It rests for a second, returns in 700 ms, and repeats at 11 seconds. Touch or wheel interaction stops the loop's commands permanently until autoplay is remounted.