Skip to main content
The SharedIGImage preset creates an Instagram-style shared element transition where an image expands from a thumbnail to fullscreen with gesture-driven dismissal.
This preset requires @react-native-masked-view/masked-view. See Masked View Setup for installation.

Function Signature

SharedIGImage(
  config: Partial<ScreenTransitionConfig> & {
    sharedBoundTag: string;
  }
): ScreenTransitionConfig

Parameters

config
object
required
Configuration object with required sharedBoundTag

Returns

ScreenTransitionConfig
object
Configuration with shared element bounds animation and gesture handling

Implementation Details

Transition Spec

Custom spring configuration for smooth shared element morphing:
transitionSpec: {
  open: {
    stiffness: 1500,
    damping: 1000,
    mass: 3,
    overshootClamping: true,
    restSpeedThreshold: 0.02,
  },
  close: {
    stiffness: 1500,
    damping: 1000,
    mass: 3,
    overshootClamping: true,
    restSpeedThreshold: 0.02,
  },
}
Higher stiffness and damping create snappier, more responsive shared element animations.

Screen Style Interpolator

The interpolator handles two separate states:

1. Focused Screen (Destination)

When the screen is focused (fullscreen image view):
if (focused) {
  const boundValues = bounds({
    id: sharedBoundTag,
    method: "content",
    scaleMode: "uniform",
    raw: true,
  });

  const maskedValues = bounds({
    id: sharedBoundTag,
    space: "absolute",
    target: "fullscreen",
    method: "size",
    raw: true,
  });

  return {
    backdropStyle: {
      backgroundColor: "black",
      opacity: interpolate(progress, [0, 1], [0, 0.5]),
    },
    contentStyle: {
      transform: [
        { translateX: dragX },
        { translateY: dragY },
        { scale: dragXScale },
        { scale: dragYScale },
      ],
    },
    _ROOT_CONTAINER: {
      transform: [
        { translateX: boundValues.translateX || 0 },
        { translateY: boundValues.translateY || 0 },
        { scale: boundValues.scale || 1 },
      ],
    },
    _ROOT_MASKED: {
      width: maskedValues.width,
      height: maskedValues.height,
      transform: [
        { translateX: maskedValues.translateX || 0 },
        { translateY: maskedValues.translateY || 0 },
      ],
      borderRadius: interpolate(progress, [0, 1], [0, 24]),
    },
  };
}

2. Unfocused Screen (Source)

When the screen is not focused (thumbnail in list):
return {
  contentStyle: {
    pointerEvents: current.gesture.isDismissing ? "none" : "auto",
  },
  [sharedBoundTag]: {
    transform: [
      { translateX: dragX || 0 },
      { translateY: dragY || 0 },
      { translateX: boundValues.translateX || 0 },
      { translateY: boundValues.translateY || 0 },
      { scaleX: boundValues.scaleX || 1 },
      { scaleY: boundValues.scaleY || 1 },
      { scale: dragXScale },
      { scale: dragYScale },
    ],
  },
};
Drag animations:
const dragX = interpolate(
  normX,
  [-1, 0, 1],
  [-width * 0.7, 0, width * 0.7],
  "clamp",
);
const dragY = interpolate(
  normY,
  [-1, 0, 1],
  [-height * 0.4, 0, height * 0.4],
  "clamp",
);
const dragXScale = interpolate(normX, [0, 1], [1, 0.8]);
const dragYScale = interpolate(normY, [0, 1], [1, 0.8]);

Usage Example

// app/feed.tsx
import { router } from "expo-router";
import Transition from "react-native-screen-transitions";

export default function FeedScreen() {
  return (
    <Transition.Pressable
      sharedBoundTag="photo-1"
      onPress={() => {
        router.push({
          pathname: "/photo",
          params: { sharedBoundTag: "photo-1" },
        });
      }}
    >
      <Image
        source={{ uri: "https://example.com/photo.jpg" }}
        style={{ width: 200, height: 200, borderRadius: 12 }}
      />
    </Transition.Pressable>
  );
}

How It Works

  1. Source tagging: Transition.Pressable with sharedBoundTag measures bounds on press
  2. Destination tagging: Transition.View with matching tag registers as animation target
  3. Masking: Transition.MaskedView clips content to animated bounds
  4. Bounds transformation: Interpolates position, size, and scale between source and destination
  5. Gesture handling: Drag applies translation and scale while maintaining bound animation

Key Features

  • Seamless morphing - Element smoothly expands from thumbnail to fullscreen
  • Backdrop fade - Black backdrop fades in as image expands
  • Drag to dismiss - Drag in any direction with scale feedback
  • Border radius animation - Corners animate from original radius to 24px
  • Masked reveal - Content outside the image bounds is clipped during transition

Notes

The sharedBoundTag must be unique within the navigation flow and must match exactly between source and destination.
Use route params to pass the sharedBoundTag dynamically when navigating to different images.
Requires Transition.MaskedView to wrap the destination screen for proper clipping. Without it, content will render incorrectly during the transition.