Skip to content
Native Animation
Portal scene picker animation preview

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

npx nativeanimation add portal-scene-picker
CarouselSkia

Portal scene picker

A scene carousel whose circular portals expand into session timers, with configurable scenes, portal size ratio, 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 scene carousel whose circular portals expand into session timers, with configurable scenes, portal size ratio, accent color, and fonts.

Why this one

The scene opens along one continuous, interruptible path. A turning planet ties selection to place, slow artwork zoom keeps each scene alive, and scene-specific mist, embers, and light motes add atmosphere. The timer has a draggable clock cursor plus accessible buttons. No audio files or playback engine are included; connect pause and timer callbacks to your app.

How it works

  1. onLayout measures the host container. The portal diameter defaults to 72% of its width, centered at 30% of its height.
  2. One continuous carousel position drives portal spacing, neighboring scale, image parallax, and planet rotation. Velocity projects the next snap, with a nearly critically damped spring.
  3. A separate opening progress expands the circular clip to twice the container diagonal. Artwork interpolates to screen dimensions while the planet exits and the title fades.
  4. The ring brightens for 120 ms, the opening takes 420 ms, and timer controls fade in over the following 400 ms. A downward drag can interrupt and reverse the opening.
  5. A clock gesture sets 5–60 minutes. UI-thread digit columns roll only during carry; state and callbacks update at integer changes.
  6. The demo advances twice at 1.2-second intervals, begins opening at 2.8 seconds, sets 25 minutes, closes, and repeats. Touch cancels autoplay.

Install

Copy this folder without demo/ into your project, then install SDK-compatible versions:

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

Use a GestureHandlerRootView at the app root. For web, initialize CanvasKit before rendering this component with Skia's documented web loader (WithSkiaWeb), and serve its WASM asset. This component imports Skia and must be loaded after that initialization.

Icons: Lucide (ISC). Paths are copied from the project kit into icons.ts.

Usage

import PortalScenePicker from './portal-scene-picker';

<PortalScenePicker
  scenes={[
    {
      id: 'forest',
      image: require('./assets/forest.jpg'),
      title: 'FOREST LIGHT',
      subtitle: 'A quiet place',
      particles: 'motes',
    },
  ]}
/>;

With personality controls and imperative playback:

const picker = useRef<PortalScenePickerRef>(null);

<PortalScenePicker
  ref={picker}
  scenes={scenes}
  portalRatio={0.72}
  openDuration={420}
  zoomIntensity={0.06}
  accentColor="#60A5FA"
  fontFamily="YourInterfaceFont"
  titleFontFamily="YourTitleFont"
  monoFontFamily="YourMonoFont"
  onPauseChange={setAudioPaused}
/>;

// Call from an event handler or effect.
picker.current?.goTo(2);
picker.current?.open();
picker.current?.setMinutes(25);
picker.current?.close();

Import useRef from React and PortalScenePickerRef from this folder in the second example. Local artwork belongs to your app; the reusable component imports no demo assets or project theme.

Props

PropTypeDefaultPurpose
scenesreadonly PortalScene[]RequiredIDs, images, titles, subtitles, and optional particle kind
initialIndexnumber0Initial scene, clamped to the list
headerReactNode—Host brand or product heading
portalRationumber0.72Portal diameter, clamped to 0.5–0.82 of width
openDurationnumber420Opening and closing milliseconds
zoomIntensitynumber0.06Slow zoom amount, clamped to 0–0.15
accentColorstring#60A5FAAtmosphere and selection ring
fontFamilystringSystemInterface font
titleFontFamilystringfontFamilyScene title font
monoFontFamilystringSystemTimer and subtitle font
onIndexChange(index: number) => void—Selection changes
onOpenChange(open: boolean) => void—Immersive mode requests
onTimerChange(minutes: number) => void—Integer timer changes
onPauseChange(paused: boolean) => void—Host playback integration
onInteraction() => void—User interaction, including autoplay cancellation
styleStyleProp<ViewStyle>—Container style; provide a bounded height
testIDstring—Root test identifier

Ref methods: goTo(index), open(), close(), setMinutes(minutes).

Compatibility

Designed for Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, Worklets 0.13.0, Gesture Handler 3.3, and Skia 2.14.0. Uses installed Expo Go modules. Supports iOS, Android, and web.

Accessibility

Scene portals and controls expose button roles, labels, and activation actions. The timer exposes adjustable semantics, current minutes, increment/decrement actions, and visible 44-point alternatives to dragging. Scene transitions announce their state, and pause status uses a polite live region. No important text or control sits in the upper-right viewer-close area.

Reduced motion disables zoom, particles, globe pulsing, parallax, and spatial scene transitions. Selection uses a 200 ms change and the immersive scene fades in; digits show integers without rolling. No blur or trails are used.