Skip to content
Native Animation
Photo Edit Scroll animation preview

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

npx nativeanimation add photo-edit-scroll

Photo Edit Scroll

A scroll-driven photo edit with cumulative adjustments and a live histogram, with configurable image, chapter copy, fonts, and grain opacity.

$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 photo edit with cumulative adjustments and a live histogram, with configurable image, chapter copy, fonts, and grain opacity.

Why this one

A conventional comparison shows two finished images. This piece explains the decisions between them. Each sweep adds one adjustment to the previous result; the histogram moves with the edit, and the final chapter introduces fine grain and a soft vignette inside the image itself.

How it works

  1. onLayout measures the container. A single continuous chapter progress comes from useAnimatedScrollHandler and the scroll offset divided by the measured height.
  2. Five Skia image layers use cumulative color matrices. RAW, Exposure, and Color use ColorMatrix filters; Contrast and Grain evaluate the same matrix coefficients inside a photo shader before the nonlinear curve (CanvasKit does not implement runtime image filters). Exposure applies a 0.40-stop gain; color adds warmth +12 and saturation 1.25; contrast adds a 1.20 gain and an S curve with a 0.04 black floor. Matrix offsets use normalized 0–1 values.
  3. Between 20% and 80% of each interval, a smoothstep clipping rectangle reveals the next image from left to right. The image stays fixed. A 2-point light edge and translucent BEFORE/AFTER pills follow the clipping boundary.
  4. Precomputed 24-bin RGB distributions interpolate with the same sweep. They describe the bundled lake photo; they are an art-directed visualization, not a live pixel-analysis tool. Replace HISTOGRAMS in settings.ts when adapting the photo.
  5. Each digit column rolls only during its carry, with distance-based fading and a final-quarter transition for continuous units. Headline windows finish before the next headline enters.
  6. The CTA is part of the scroll content. Photo taps offer chapter navigation, native snapping stays disabled on web, and reduced motion replaces the curtain with 200 ms chapter fades.

Install

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

Copy this folder without demo/ into your project. Use your project's Reanimated/worklets configuration. On web, initialize CanvasKit before importing/rendering the component, for example with Skia's WithSkiaWeb loader. The catalog host must register this demo with its Skia-aware lazy loader.

Icons: Lucide (ISC). The demo identity uses the supplied Orrin kit mark. No fonts or demo assets are imported by the reusable component.

Usage

import { PhotoEditScroll } from './photo-edit-scroll';

export default function EditorStory() {
  return <PhotoEditScroll source={require('./lake.jpg')} />;
}

With typography, copy, and personality controls:

<PhotoEditScroll
  source={require('./lake.jpg')}
  fontFamily="Geist_400Regular"
  monoFontFamily="GeistMono_400Regular"
  titleFontFamily="InstrumentSerif_400Regular"
  italicFontFamily="InstrumentSerif_400Regular_Italic"
  grainOpacity={0.06}
  snapToChapters={false}
  chapterCopy={[
    { title: 'Start with light.', subtitle: 'Keep every detail.' },
    { detail: 'Raise exposure by 0.40 stops.' },
  ]}
  actionLabel="Save the recipe"
  onComplete={() => saveRecipe()}
/>

Load custom fonts in your app before rendering. The host container needs a bounded height; the component fills it with flex: 1.

Props

PropTypeDefaultDescription
sourceDataSourceParamRequiredLocal photo or Skia-compatible image source.
chapterCopyreadonly PhotoEditChapterCopy[]undefinedFive optional copy overrides, ordered RAW through Grain.
fontFamilystringundefinedInterface font family.
titleFontFamilystringundefinedEditorial headline font family.
italicFontFamilystringundefinedItalic editorial font family.
monoFontFamilystringundefinedFont family for tabular values.
colorScheme'dark' | 'light' | 'auto''dark'Appearance; auto follows the device.
snapToChaptersbooleantrueChapter snapping on iOS and Android only.
grainOpacitynumber0.08Scales the final texture opacity.
actionLabelstring'Keep this edit'CTA label.
headerReact.ReactNodeundefinedOptional top-left identity supplied by the host.
onComplete() => voidundefinedCalled when the final CTA is pressed.
onChapterChange(chapter: number) => voidundefinedCalled after a chapter becomes active.
onInteraction() => voidundefinedCalled immediately when a finger, mouse, or wheel interrupts playback.
styleStyleProp<ViewStyle>undefinedContainer style.
testIDstringundefinedTest identifier.
durationnumber750Scroll hint travel duration in milliseconds. Spring physics and reduced-motion timing are unchanged.

PhotoEditScrollRef exposes goToChapter(index), scrollToProgress(progress), and reset(). Continuous progress is clamped to 0–4. Autoplay belongs to the demo: a cancellable animation-frame clock drives actual scroll with (0.77, 0, 0.175, 1) easing, one chapter every 2.6 seconds. After the final hold, a fade to black hides the return to RAW. Touch or wheel input stops the clock.

Compatibility

Targets Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, worklets 0.13.0, and Skia 2.14.0. Uses the installed Expo Go-compatible stack; no additional native dependency is added. Supports iOS, Android, and web.

Accessibility

Each scroll chapter has an accessible header containing its title and concrete adjustment. The photo is a large labeled button for advancing a chapter; the final photo returns to RAW. The CTA and restart control have button roles and minimum 44-point hit areas. Decorative canvas, curtain labels, and rolling text are excluded from the accessibility tree. Reduced motion removes the sweeping curtain and bouncing scroll hint, replaces chapter transitions with 200 ms fades, and disables animated imperative scrolling. There is no parallax, zoom, or velocity trail.