Skip to content
Native Animation
Product Zoom Gallery animation preview

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

npx nativeanimation add product-zoom-gallery

Product Zoom Gallery

A product gallery with focal zoom, elastic panning, and color selection, with configurable variants, copy, background 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 product gallery with focal zoom, elastic panning, and color selection, with configurable variants, copy, background color, and fonts.

Why this one

Zoom stays anchored to the touched point. Panning carries momentum into soft boundaries instead of jumping on release. The stage tint and thumbnail indicator follow the same continuous variant index, while product details move out of the way during inspection.

How it works

  1. Measure the container with onLayout; stage geometry follows the available space.
  2. Drive bottle position, perspective, radial tint, and thumbnail indicator from one shared variant index.
  3. Compose exclusive double and single taps simultaneously with pinch and pan. Pinch uses its focal point in stage coordinates.
  4. Switch the pan from variant browsing to image translation above 1.02×. Apply elastic resistance outside the bounds, then decay within clamps on release.
  5. Derive stage expansion and detail visibility from zoom. Expose ref methods for host controls and demo playback.

Install

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

Wrap your app in GestureHandlerRootView. For web, initialize CanvasKit before importing this component (for example through Skia's WithSkiaWeb deferred entry). Keep transparent images bundled locally and crop unused transparent margins. No demo images or fonts are required by the reusable component.

Usage

import ProductZoomGallery from './product-zoom-gallery';

<ProductZoomGallery
  variants={[
    { name: 'Green', color: '#8FA98F', image: require('./green.png') },
    { name: 'Clay', color: '#C9714F', image: require('./clay.png') },
  ]}
  title="Trail Bottle 24 oz"
  onAddToBag={(index) => addVariantToCart(index)}
/>
const gallery = useRef<ProductZoomGalleryRef>(null);

<ProductZoomGallery
  ref={gallery}
  variants={variants}
  doubleTapScale={2.4}
  maxZoom={3}
  springDamping={28}
  backgroundColor="#F4F7F6"
  textColor="#0F2E2A"
  fontFamily="YourInterfaceFont"
  titleFontFamily="YourEditorialFont"
  monoFontFamily="YourPriceFont"
/>
// From a host button or event handler:
gallery.current?.zoomTo(2.2, 0.5, 0.18);

Props

PropTypeDefaultDescription
variantsProductVariant[]requiredName, hex color, and image per variant. Empty lists are supported.
headerReactNode—Host product-page header.
titlestringProductProduct heading.
descriptionstringInsulated steelMaterial shown after the active color.
pricestring$42.00Formatted price.
ratingstring4.8Formatted rating.
reviewCountstring(1,284)Formatted review count.
actionLabelstringAdd to bagPurchase label and accessible name.
onAddToBag(index: number) => void—Purchase event.
onIndexChange(index: number) => void—Nearest variant changes, on the JS thread.
onZoomChange(zoomed: boolean) => void—Browsing/zoomed state changes, on the JS thread.
fontFamilystringsystemInterface typeface.
titleFontFamilystringsystemEditorial typeface.
monoFontFamilystringsystemPrice typeface.
backgroundColorstring#F4F7F6Page background.
textColorstring#0F2E2AText and CTA color.
springDampingnumber28Spring damping; stiffness 300 and mass 1. Values below the damping needed for ζ ≥ 0.8 are raised to that minimum.
doubleTapScalenumber2.2Double-tap zoom, clamped to maximum.
maxZoomnumber3Maximum scale, at least 1.
accessibilityLabelstringProduct galleryStage description.
styleStyleProp<ViewStyle>—Root container styling.
testIDstring—Root identifier.

Ref methods: goTo(index), zoomTo(scale, x = 0.5, y = 0.5), driftTo(x, y), and resetZoom(). Focal coordinates are normalized; drift coordinates are normalized translations, clamped to zoom bounds. goTo also exits zoom.

Compatibility

Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, Worklets 0.13.0, and Skia 2.14.0. On web, defer the Skia module until CanvasKit is ready.

Accessibility

Color thumbnails and the Zoom/Reset button provide touch alternatives to swiping and pinching, with hit areas of at least 44 pt in the supplied demo sizes. Buttons expose roles, labels, and selection state. Product details hidden during zoom are removed from accessibility traversal. Reduced motion fades variant changes, removes perspective and carousel travel, uses immediate host zoom or short transitions, and disables panning inertia. The stage includes a gesture hint; a single tap stops image inertia.

Demo timeline and layout

The demo uses the kit's Tidewell tile and Lucide glyph. Local bottle alpha bounds were measured and cropped to 184×597 px from the supplied images. Icons: Lucide (ISC).

Autoplay: zoom onto the cap at 0.38 s, drift at 0.93 s, reset at 1.73 s, select Clay at 2.33 s, select Butter at 3.98 s; repeat after 5.4 s. Timers are cleaned up on unmount.

Five 56 pt thumbnails fit both widths. Product copy starts at 70%, with a 35.4 pt fitted title, 15 pt description, 22 pt price, and 56 pt CTA at 88%. The small layout has about 25 pt between the price row and CTA. The cap zoom and Clay tint change occur within the first three seconds. The stage uses a real radial shader; no opaque effect block overlays text.