About this animation
A full-screen artwork selector whose portrait thumbnails rotate along a tactile arc and launch each new scene from the chosen image.
Why this one
Most full-screen selectors fade between static rows of thumbnails. This one turns the row into an inertial dial: portraits rise, enlarge, and gain a luminous ring at the apex, while the next artwork expands directly from its thumbnail in a cinematic circular reveal. Neighboring full-resolution images are prefetched so remote artwork is ready before selection.
How it works
- The component measures its own container, then derives the arc center, radius, thumbnail diameter, and full-screen reveal from those dimensions.
- One continuous
positionshared value drives every thumbnail's angle, coordinates, scale, opacity, and stacking order. - Horizontal dragging maps distance to angular travel. Release velocity feeds a decay animation, then a spring snaps the nearest portrait to the top of the arc.
- Selection mounts the new full-screen image inside a circular, clipped layer centered on the chosen thumbnail. The circle grows from
0.15Wto2.4Hin 650 ms. - The previous and next remote images are prefetched into Expo Image's memory and disk cache after every selection.
Install
npx expo install expo-image expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-workletsUsage
import ArcAvatarSwitcher from './ArcAvatarSwitcher';
export function GalleryScreen() {
return (
<ArcAvatarSwitcher
items={[
{ image: require('./art/drift.jpg'), label: 'Drift' },
{ image: require('./art/bloom.jpg'), label: 'Bloom' },
{ image: require('./art/skyward.jpg'), label: 'Skyward' },
]}
/>
);
}Customize the dial and control selection from a ref:
import { useRef } from 'react';
import ArcAvatarSwitcher, {
type ArcAvatarSwitcherRef,
} from './ArcAvatarSwitcher';
export function CuratedGallery() {
const switcherRef = useRef<ArcAvatarSwitcherRef>(null);
return (
<ArcAvatarSwitcher
ref={switcherRef}
items={artworks}
initialIndex={2}
arcRadius={0.82}
thumbSize={0.17}
ringColor="#B7FFDA"
fontFamily="YourSansMedium"
onIndexChange={(index) => trackSelection(index)}
/>
);
}The forwarded ref exposes select(index).
Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | { image, thumb?, label }[] | Required | Full-screen images, optional thumbnail crops, and accessible labels. |
initialIndex | number | 0 | Initially selected item. |
arcRadius | number | 0.9 | Arc radius as a fraction of component width. |
thumbSize | number | 0.15 | Thumbnail diameter as a fraction of component width. |
ringColor | string | '#FFFFFF' | Active thumbnail ring color. |
fontFamily | string | System font | Font used for the active label. |
onIndexChange | (index: number) => void | — | Called when the reveal completes. |
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 and Worklets 0.10
- iOS: supported, not yet verified on a device
- Android: supported, not yet verified on a device
- Web: supported, not yet verified
- Works in Expo Go because its native dependencies are included in the SDK
Accessibility
Every thumbnail is a labeled button and reports its selected state. The dial also exposes adjustable increment and decrement actions for screen readers, so every drag has a tap and assistive-technology alternative. With reduced motion enabled, inertial travel becomes a short timing transition and the expanding circle becomes a 200 ms full-screen fade.