Skip to content
Native Animation
Split Columns Scroll animation preview

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

npx nativeanimation add split-columns-scroll
Skia

Split Columns Scroll

A photo story with opposing image and text scroll directions, with configurable chapters, background color, column gap, 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 photo story with opposing image and text scroll directions, with configurable chapters, background color, column gap, and fonts.

Why this one

Instead of placing a photo above a caption, the columns move toward each other. A real Skia saturation matrix restores color as each photo aligns, while an accent stroke traces its perimeter. The final photograph expands across the gutter and becomes the closing canvas.

How it works

  1. onLayout measures the containing viewport; no global screen dimensions are used.
  2. useAnimatedScrollHandler turns scroll distance into one continuous progress value, from 0 to 3. Chapter centers are 0.2, 1, 1.8, and 2.6; the remaining distance completes the closing frame.
  3. Each chapter's local phase has a ±20% hold, with smoothstep between holds. The photo moves down and its note moves up.
  4. Headline entrance and exit windows do not overlap. Background images enter over the previous image, with top and bottom dark veils.
  5. ColorMatrix restores saturation near alignment; the perimeter path draws with the same progress. The last frame grows to the viewport between 2.72 and 3.
  6. The closing action stays inside the ScrollView. Ref methods drive real scroll offsets; autoplay belongs to the demo and stops when the user takes over.

Install

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

Copy this folder without demo/. Load Skia's CanvasKit before rendering on web; in Expo use the host app's Skia web loading boundary. Supply local image modules and preload custom fonts before mounting. Icons: Lucide (ISC); see the source attribution in icons.ts.

Usage

import { SplitColumnsScroll } from './split-columns-scroll';

const chapter = {
  image: require('./assets/coast.jpg'),
  title: 'First light.',
  emphasis: 'Keep looking.',
  eyebrow: 'COAST',
  copy: 'Walk 2 miles. Watch the horizon.',
  settings: 'f/2.8 · 1/500 · ISO 100',
  location: 'Atlantic coast · 6:12 AM',
};

<SplitColumnsScroll chapters={[chapter, chapter, chapter, chapter]} />;

With personality controls and an imperative ref:

const story = useRef<SplitColumnsScrollRef>(null);
<SplitColumnsScroll
  ref={story}
  chapters={myFourChapters}
  accentColor="#E6AD78"
  columnGap={8}
  photoHeightRatio={0.64}
  titleFontFamily="MySerif"
  italicFontFamily="MySerifItalic"
  ctaLabel="Explore the collection"
  onCtaPress={openCollection}
/>;
// Call from an event or a demo timer.
story.current?.scrollToProgress(1.8, 1500);

Import useRef from React and SplitColumnsScrollRef from this folder for the second example.

Props

PropTypeDefaultPurpose
chaptersreadonly SplitColumnsChapter[]requiredFour chapters with image, title, emphasis, eyebrow, copy, settings, location, and optional accent
headerReactNodenoneBrand content; stays left of the viewer close control
collectionLabelstringField notesCollection caption
closingTitle / closingEmphasisstringMade to remember. / Every frame.Closing headline
ctaLabelstringExploreClosing button and accessible label
onCtaPress() => voidnoneAction callback
onInteraction() => voidnoneNotify a demo clock to stop
onChapterChange(index: number) => voidnoneActive chapter callback on JS
backgroundColor / textColor / accentColorstring#111111 / #F5F5F4 / #F5C26BPalette
columnGapnumber6Gutter in points
photoHeightRationumber0.62Photo height relative to the viewport
fontFamily / titleFontFamily / italicFontFamily / monoFontFamilystringsystemLoaded font family names
snapToChaptersbooleantrueNative-only snapping; web keeps continuous wheel scroll
accessibilityLabelstringPhoto field notesStory label
style / testIDStyleProp<ViewStyle> / stringnoneRoot customization

The ref exposes scrollToProgress(progress, duration?), next(), stop(), and reset(). Default scroll duration is 900 ms; reduced motion uses 200 ms. Reset fades to black before returning to the top.

Compatibility

Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, Worklets 0.13.0, Skia 2.14.0. Compatible with the installed Expo Go modules. Supports iOS, Android, and web.

Accessibility

Each chapter has a readable accessibility block and a heading in the scroll content. The next-chapter buttons provide a tap alternative to scrolling with 44-point minimum hit targets. The final action has a button role, a label, and independent press feedback. Reduced motion removes column travel, zoom, and hint displacement; photos transition with 200 ms fades. Custom fonts are optional and no React state changes per animation frame.

Demo timeline

At 150 ms, scroll toward chapter one over 1.3 seconds: the two columns meet, the image gains color, and the frame draws within the first 3 seconds. Later chapters begin every 2.6 seconds. The closing expansion follows the fourth chapter, holds for the CTA, then resets through black. Touch, drag, mouse wheel, and chapter buttons stop the autoplay clock.