Skip to content
Native Animation
Sticky Device Scroll animation preview

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

npx nativeanimation add sticky-device-scroll

Sticky Device Scroll

A scroll-driven device story that changes app screens in place, with configurable chapters, task data, 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 scroll-driven device story that changes app screens in place, with configurable chapters, task data, accent color, and fonts.

How it works

  1. Measure the component and scroll viewport with onLayout. Portrait layouts center the device above full-width editorial copy; landscape layouts place copy beside it. Device width defaults to 80% of the container, capped at 360 pt. Its frame adapts to the available height instead of shrinking a fixed screenshot.
  2. Measure chapter copy and action areas in normal layout. All chapters use the tallest measured content, so larger type and longer copy remain reachable by scrolling.
  3. Derive one continuous story progress from the scroll offset. When a chapter exceeds the viewport, hold its scene while the reader scrolls through the overflow, then morph toward the next chapter. Editorial copy fades out according to its clearance from the device, before either can overlap. The device returns to its top alignment before the next screen morph begins, so every new screen heading is fully visible.
  4. Keep the five task cards mounted. Their bounds interpolate from list rows into board lanes; the first expands into a focus surface, and the timer ring becomes the tallest weekly bar.
  5. Native scroll snapping uses chapter stops and reading stops for overflow. Web waits until wheel input settles before snapping, avoiding mandatory CSS snapping on every wheel tick. Every numbered step and next-step action also jumps directly to its chapter with an interruptible spring. Programmatic scrolling settles only after both the animation and the scroll offset reach the requested destination.
  6. Reduced motion switches scene geometry immediately, removes device rotation, and makes programmatic jumps instant. Demo autoplay cycles through chapters 0–3 and resets to chapter 0. It is disabled with reduced motion and stops on user input.

The material combines a dark device frame, a fine light edge, two-layer shadows, and a violet ambient gradient. No photo assets or network requests are required. Nimbus branding exists only in the demo.

Install

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

Target versions: Expo SDK 58, React Native 0.88.0, Skia 2.14.0, Linear Gradient ~58.0.3, Reanimated 4.7.1, and Worklets 0.13.0. Web requires CanvasKit initialization before mounting, using Skia's WithSkiaWeb integration. Load custom fonts in the host app.

Usage

import StickyDeviceScroll from './sticky-device-scroll';

export function ProductStory() {
  return <StickyDeviceScroll onCtaPress={() => openSignup()} />;
}
import { useRef } from 'react';
import {
  StickyDeviceScroll,
  type StickyDeviceScrollRef,
} from './sticky-device-scroll';

export function CustomStory() {
  const story = useRef<StickyDeviceScrollRef>(null);
  return (
    <StickyDeviceScroll
      ref={story}
      colorScheme="dark"
      accentColor="#9C85FF"
      deviceWidthRatio={0.84}
      navigationHint="Swipe up or choose a step"
      nextLabel="Continue"
      ctaLabel="Start your next chapter"
      fontFamily="YourInterfaceFont"
      titleFontFamily="YourEditorialFont"
      italicFontFamily="YourEditorialItalicFont"
      monoFontFamily="YourNumberFont"
      screenCopy={{ timer: '15:00', total: '8.5 hrs' }}
      onCtaPress={() => openSignup()}
      testID="product-story"
    />
  );
}

Props

PropTypeDefaultDescription
chaptersFour DeviceChapter objectsGeneric productivity storyEyebrow, title, subtitle, and body for each step. Eyebrows also label the device screens and step controls. Keep these short.
tasksFive DeviceTask objectsGeneric launch tasksStable ID, title, initials, and board column (0–2). The first task becomes the focus task. Device rows abbreviate long titles; editorial copy stays in flow.
brandReactNode—Optional host-supplied identity in the upper-left header.
ctaLabel, ctaNotestringGet started, No card neededFinal action and supporting copy, integrated into step four.
onCtaPress() => void—Final action callback.
onChapterChange(chapter: number) => void—Nearest story chapter changes, indexed 0–3.
onInteraction() => void—User touch, drag, wheel, or control press.
accentColorstring#6E56FFSix-digit hex accent for the ambient gradient, progress, cards, ring, and chart.
colorSchemelight | dark | autolightComplete background, surface, ink, muted text, border, and lane palette.
fontFamilystringSystemUI and body font.
titleFontFamily, italicFontFamilystringSystem / title fontEditorial statement and accent line.
monoFontFamilystringSystemStep numbers and device statistics.
deviceWidthRationumber0.8Relative frame width, clamped to 0.45–0.94, with a 360 pt maximum. Landscape uses the device column width.
snapToChaptersbooleantrueSnap after scroll input; chapter controls work regardless.
navigationHintstringSwipe up to explore · Tap a step to jumpVisible navigation instruction.
nextLabelstringNext stepVisible and accessible next-step label.
chapterAccessibilityLabel(chapter, index) => stringStep N of 4: [eyebrow]Localizable labels for numbered chapter controls.
screenCopyPartial<DeviceScreenCopy>Generic device copyFour subtitles, three board columns, timer, timer caption, weekly total, comparison, and seven day labels.
style, testIDStyleProp<ViewStyle>, string—Container styling and root identifier. Controls append -step-0 through -step-3, -next-0 through -next-2, -confirm, and -scroll.

Ref methods: scrollToChapter(index, duration?), stop(), and reset(). Index 0–3 selects a chapter; index 4 scrolls to the final action in chapter four. Omit duration for an interruptible spring; supply milliseconds for autoplay timing. reset() returns immediately to the beginning. stop() cancels programmatic scrolling.

Compatibility

The component fills its parent's available space; the host provides safe-area insets. The header reserves the upper-right 56 pt for viewer controls. Tablet widths are capped; landscape uses two columns. Small containers and large text use overflow reading stops rather than shrinking editorial text. Resize handling measures the new available layout.

Targets Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, Worklets 0.13.0, Skia 2.14.0, and Expo Linear Gradient ~58.0.3. Supports iOS, Android, and web with Expo Go-compatible native dependencies; web needs the CanvasKit setup above.

Accessibility

Chapter controls have a selected state, numbered labels, and a minimum 48 pt height. Next-step and final actions also have 48 pt targets and button roles. Editorial titles have header roles; font scaling stays enabled. Decorative device content is excluded from screen readers. All screen labels and values are supplied through chapter data, tasks, or screenCopy. Reduced motion disables autoplay, rotation, springs, and interpolated scene geometry.

Icons: Lucide (ISC), copied from the approved project kit.