About this animation
A React Native onboarding animation can travel from a broad photographic view into a close destination before revealing its final action. This component moves through nested image levels with one continuous Reanimated journey that also controls titles, friend markers, distance counters, the final badge, and the call to action. Its camera accelerates, cruises, and decelerates across the whole sequence rather than restarting speed at each image handoff. Use it for a nearby-community introduction, location discovery, a photographic narrative, or a staged first-run journey. Supply at least two image levels and optionally up to three friends, then customize the final name, action label, accent, fonts, and travel time per level. Connect completion and action callbacks, or replay and skip through the forwarded ref.
When to use it
- Introduce a nearby community.
- Travel through photographic onboarding levels.
- Reveal people at a final destination.
- Build a staged location-discovery introduction.
A cinematic onboarding sequence that travels through nested photographic worlds before revealing nearby friends and a bold brand screen.
How it works
- One continuous
journeyshared value drives the complete sequence, so images, titles, friend markers, counters, the brand screen, and the CTA cannot drift onto separate clocks. - The camera uses a continuous velocity profile: a gentle acceleration, a constant-speed cruise, and a long deceleration into the final image. Every layer reads the same camera position, which removes speed resets at level boundaries.
- Titles are derived directly from that camera position and cross-fade while traveling a short distance, with no React state update or independent animation between levels.
- After the camera stops, friend markers settle in with a restrained spring-shaped path. The completed scene then holds before the brand badge and CTA enter.
- Reduced Motion keeps the narrative order but replaces scaling and overshoot with cross-fades and shorter holds.
Timing
levelSeconds controls the camera travel time per transition. With the four-level demo and the default 1.8, the timing is:
| Phase | Duration | Motion |
|---|---|---|
| Camera travel | 3 × 1.8s = 5.4s | First 14% accelerates smoothly, the middle cruises at constant speed, and the final 32% decelerates continuously. |
| Arrival pause | 150ms | The final photograph becomes completely still before markers enter. |
| Friend markers | 500ms each, staggered by 100ms | Three markers finish in 700ms; their position, scale, and distance counter share the main journey. |
| Final breath | 650ms | The complete last scene rests with no new movement. |
| Brand and CTA | 700ms | The brand surface fades in first; the CTA follows during the latter 62% of the reveal. |
The default four-level sequence therefore finishes in 7.6s. A different number of levels changes only the camera-travel portion. Reduced Motion uses 250ms marker and reveal phases, a 40ms marker stagger, and a 250ms final breath.
Why this one
Many infinite-zoom intros animate each layer as a separate clip, creating tiny pauses or acceleration resets that become especially noticeable near the end. This implementation treats the entire piece as one choreographed journey, preserves velocity through every photographic handoff, and gives the destination enough time to settle before asking the viewer to act. The restrained type movement, soft depth, single accent, and delayed white CTA keep the cinematic effect legible instead of turning the finish into a pileup of competing animations.
Install
npx expo install react-native-reanimated react-native-worklets expo-image @shopify/react-native-skiaOn web, load Skia before rendering this component. The Native Animation catalog handles that asynchronous setup at its app boundary.
Usage
import InfiniteZoomIntro from './InfiniteZoomIntro';
const levels = [
{ image: require('./overview.jpg'), title: 'The world' },
{ image: require('./neighborhood.jpg'), title: 'Close to home' },
];
export function IntroScreen() {
return <InfiniteZoomIntro levels={levels} />;
}Customize the pacing, people, typography, and final action or control playback through its ref:
import { useRef } from 'react';
import InfiniteZoomIntro, { type InfiniteZoomIntroRef } from './InfiniteZoomIntro';
export function SocialIntro() {
const introRef = useRef<InfiniteZoomIntroRef>(null);
return (
<InfiniteZoomIntro
accentColor="#5B4BFF"
brand="Together"
ctaLabel="Find my circle"
fontFamily="YourSansFont"
friends={friends}
levelSeconds={2.1}
levels={levels}
onCtaPress={() => continueOnboarding()}
onFinish={() => trackIntroComplete()}
ref={introRef}
titleFontFamily="YourDisplayFont"
/>
);
}The forwarded ref exposes replay() and skip().
Props
| Prop | Type | Default | Description |
|---|---|---|---|
levels | ZoomLevel[] | Required | Two or more photographic levels, ordered from outermost to closest. |
friends | { avatar: ImageSourcePropType; distance: string }[] | [] | Up to three people revealed on the last level. |
brand | string | 'Nearby' | Name inside the final badge. |
ctaLabel | string | 'Get me in' | Fixed bottom action label. |
accentColor | string | '#3D5AFE' | Color used by distance pills, the action, and the final screen. |
levelSeconds | number | 1.8 | Duration of each zoom segment in seconds. |
titleFontFamily | string | System font | Font used by level titles and the brand. |
fontFamily | string | System font | Font used by captions, distances, and the action. |
onCtaPress | () => void | — | Called when the bottom action is pressed. |
onFinish | () => void | — | Called after the final brand screen fades in. |
style | StyleProp<ViewStyle> | — | Outer-container style. |
testID | string | — | Test identifier for the outer container. |
Compatibility
- Expo SDK 57
- React Native 0.86
- Reanimated 4.5, Worklets 0.10, and React Native Skia 2.6
- iOS: supported, not yet verified on a device
- Android: supported, not yet verified on a device
- Web: supported, verified by the project build
- Works in Expo Go because its native dependencies are included in Expo SDK 57
Accessibility
The action is exposed as a labeled button, titles are announced as headings, and friend markers include their distance. With Reduce Motion enabled, image levels cross-fade without scaling, markers fade without bouncing, and the badge remains still.
FAQ
- How many image levels does the infinite zoom intro need?
- Supply at least two levels, ordered from outermost to closest. Each level contains its image and title; friends can add up to three avatars and distance labels to the final scene.
- Can I change the zoom speed or skip the introduction?
- Yes. levelSeconds sets travel time per transition, while the ref exposes replay and skip. onFinish runs after the final screen fades in and onCtaPress handles the action.
- Does the onboarding zoom support Reduce Motion?
- Yes. Image levels crossfade without scaling, markers fade without bouncing, and the badge stays still. The narrative order remains intact with shorter holds.




