About this animation
A scroll-driven landing page with assembling iridescent objects and configurable sections, colors, fonts, and motion intensity.
Why this one
The artwork responds to your thumb: seven individually delayed blocks build a dimensional figure and dismantle in reverse. Coins follow a shared cubic ribbon with an arc-length lookup, so their rotation matches traveled distance. A lavender stage and warm orbital accents give the three chapters their own composition.
How it works
- The container measures itself with
onLayout; section positions come from document flow rather than screen constants. - A native Reanimated ScrollView writes a single offset on the UI thread. Every scene derives its progress from that offset.
- Skia blocks interpolate fall, rotation, opacity, and a two-point settling motion independently. Gradients shade their faces and top edges.
- A sampled cubic arc-length table drives three circular artwork crops along the ribbon. Rotation uses distance divided by coin radius.
- The final image moves with parallax while gold rings and orbit markers rotate behind it. Headings fade into view, with a blur filter on supported platforms.
- Imperative playback uses the same scroll offset. A touch or drag immediately cancels it; buttons provide a tap alternative.
Install
npx expo install @shopify/react-native-skia expo-blur expo-image expo-linear-gradient react-native-reanimated react-native-workletsConfigure Reanimated/worklets as required by your Expo project. For web, initialize Skia through the host's withSkia loading boundary and serve its matching canvaskit.wasm. The component does not load fonts or apply safe-area padding; the host owns both.
Usage
import { useRef } from 'react';
import { IridescentScrollSections, type IridescentScrollSectionsRef } from './iridescent-scroll-sections';
const sections = [
{ id: 'build', title: 'Build something lasting', text: 'One thoughtful layer at a time.', object: 'blocks' as const },
{ id: 'flow', title: 'Keep moving forward', text: 'Less friction. More possibility.', object: 'ribbon' as const, image: require('./coin.jpg') },
{ id: 'story', title: 'Our story', text: 'A fresh perspective, built around you.', object: 'pedestal' as const, image: require('./pedestal.jpg') },
];
export function Landing() {
const ref = useRef<IridescentScrollSectionsRef>(null);
return <IridescentScrollSections ref={ref} sections={sections} />;
}Customize the material and behavior:
<IridescentScrollSections
sections={sections}
colorScheme="auto"
colors={{ accent: '#B28BF4', gold: '#D5B56A' }}
motionIntensity={0.8}
brandName="Your studio"
actionLabel="Explore plans"
onGetStarted={openPlans}
onSectionChange={trackChapter}
/>Props
| Prop | Type | Default | Purpose |
|---|---|---|---|
sections | IridescentSection[] | Required | IDs, titles, body copy, object types, optional eyebrows and local images. |
colors | Partial<IridescentScrollColors> | Light palette | Background, atmosphere, ink, muted, accent, pink, cream, gold, glass, border, brandInk, button and buttonText. |
colorScheme | 'light' | 'dark' | 'auto' | 'light' | Complete theme including header material. |
brandName | string | 'Studio' | Header wordmark. |
brandGlyph | string | Kit sparkle | SVG path for the brand tile. |
brandGradient | [string, string] | Purple kit gradient | Brand tile colors. |
titleFontFamily | string | System | Heading face; demo uses Geist Light. |
fontFamily | string | System | Body and action face. |
monoFontFamily | string | System | Eyebrows and scroll prompt. |
brandFontFamily | string | Body face | Wordmark face. |
actionLabel | string | 'Get started' | Final CTA and accessible label. |
menuLabel | string | 'Explore sections' | Accessible label for the next-chapter header control. |
nextLabel | string | 'Scroll down' | Accessible chapter navigation label. |
scrollLabel | string | 'SCROLL DOWN ↓' | Visible navigation prompt. |
motionIntensity | number | 1 | Fall, rotation, and parallax strength, clamped to 0–1.5. |
onScroll | (offset: number) => void | — | JS callback throttled to 30-point offset buckets. |
onInteraction | () => void | — | Touch/drag notification; stop external autoplay here. |
onSectionChange | (index: number) => void | — | Zero-based chapter change. |
onGetStarted | () => void | — | Final action. |
footer | ReactNode | — | Additional content in scroll flow. |
style | StyleProp<ViewStyle> | — | Host container overrides. |
testID | string | 'iridescent-scroll-sections' | Root and stable control identifiers. |
ref.scrollToSection(index, durationMs?) uses an interruptible spring by default, or explicit timing for playback, and accepts zero-based fractional indexes for continuous playback and clamps to the available chapters. ref.stop() interrupts at the current position. The menu cycles through chapters; it does not open a drawer.
Compatibility
Targets Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, worklets 0.13.0, and Skia 2.14.0. Android header blur uses SDK 58's BlurTargetView setup with an SDK 31+ fallback.
Accessibility
Controls have button roles, editable labels, 48-point minimum targets, and independent 0.97 press feedback. Heading changes are announced to screen readers. Reduced motion keeps scenes in static poses, removes rolling, falling, ring rotation, and parallax, jumps programmatic scroll to the destination, and fades headings over 200 ms. Text remains in document flow and respects font scaling.
Layout and performance
The root fills its host. Artwork is capped at 340 points; sections scroll naturally in compact containers and landscape. Header controls reserve the upper-right viewer area. Native scrolling and Skia shared values avoid React updates per frame; React state only stores layout measurements. The demo's 1024-square local JPEGs cover the maximum displayed artwork at 3×. Timers are cleaned up and autoplay stops on touch. The roughly 11-second loop builds the blocks within its first three seconds, visits the remaining chapters, pauses on the CTA, and returns upward.
No remote artwork, audio, currency symbols, or reference branding is included. Images are supplied by the host; without them the component renders palette gradients. Dark mode does not recolor supplied raster artwork.
Assets and license
Demo artwork is supplied project art and stays outside the copied component. Icons: Lucide (ISC), copied from the project kit. The Aster demo identity uses the kit's sparkle tile and gradient. Component licensing follows the catalog's Native Animation License; no separate license is granted here.




