Trigger-Based Modal Animations
GestureTrigger supports smooth trigger-based animations that create seamless transitions from trigger elements (like thumbnails) to the full modal view. This feature enhances user experience by maintaining visual continuity between the trigger and modal content.
GestureTrigger Usage
The GestureTrigger component wraps pressable elements and registers their position for smooth modal transitions.
import { GestureTrigger, GestureViewer } from 'react-native-gesture-image-viewer';
function Gallery() {
  const [visible, setVisible] = useState(false);
  const [selectedIndex, setSelectedIndex] = useState(0);
  const openModal = (index: number) => {
    setSelectedIndex(index);
    setVisible(true);
  };
  return (
    <View>
      {/* Gallery Grid */}
      {images.map((uri, index) => (
        <GestureTrigger key={uri} id="gallery" onPress={() => openModal(index)}>
          <Pressable>
            <Image source={{ uri }} />
          </Pressable>
        </GestureTrigger>
      ))}
      {/* Modal */}
      <Modal visible={visible} transparent>
        <GestureViewer
          id="gallery"
          data={images}
          initialIndex={selectedIndex}
          renderItem={renderImage}
          onDismiss={() => setVisible(false)}
          triggerAnimation={{
            duration: 300,
            easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
            onAnimationComplete: () => console.log('Animation finished!')
          }}
        />
      </Modal>
    </View>
  );
}
 
1. Using onPress on GestureTrigger (Recommended)
<GestureTrigger onPress={() => openModal(index)}>
  <Pressable>
    <Image source={{ uri }} />
  </Pressable>
</GestureTrigger>
 
2. Using onPress on Child Element
<GestureTrigger>
  <Pressable onPress={() => openModal(index)}>
    <Image source={{ uri }} />
  </Pressable>
</GestureTrigger>
 
important
- Both methods are functionally equivalent
 
- The 
GestureTrigger will automatically inject the press handler into the child component 
- The child component must be pressable (support onPress prop)
 
- If both 
GestureTrigger and child have onPress, both will be called (child's handler first) 
Supported Child Components
You can use any pressable component as a child, such as:
Pressable 
TouchableOpacity 
TouchableHighlight 
Button 
- Any custom component that accepts 
onPress prop 
Example with Different Components
// Using TouchableOpacity
<GestureTrigger onPress={handlePress}>
  <TouchableOpacity>
    <Text>View Image</Text>
  </TouchableOpacity>
</GestureTrigger>
// Using custom component
function CustomButton({ onPress, children }) {
  return (
    <Pressable onPress={onPress}>
      {children}
    </Pressable>
  );
}
<GestureTrigger>
  <CustomButton onPress={handlePress}>
    <Text>Custom Button</Text>
  </CustomButton>
</GestureTrigger>
 
Animation Configuration
You can customize the trigger animation behavior using the triggerAnimation prop:
import { ReduceMotion, Easing } from 'react-native-reanimated';
function App() {
  return (
    <GestureViewer
      triggerAnimation={{
        duration: 400, // Animation duration in ms
        easing: Easing.bezier(0.25, 0.1, 0.25, 1.0), // Custom easing function
        reduceMotion: ReduceMotion.System, // Respect system reduce motion
        onAnimationComplete: () => { // Callback when animation finishes
          console.log('Modal animation completed');
        }
      }}
    />
  )
}
 
Multiple Trigger Instances
You can have multiple trigger instances by using different IDs:
// Photo gallery triggers
<GestureTrigger id="photos" onPress={() => openPhotoModal(index)}>
  <Image source={photo} />
</GestureTrigger>
// Video gallery triggers
<GestureTrigger id="videos" onPress={() => openVideoModal(index)}>
  <Video source={video} />
</GestureTrigger>
 
TIP
Make sure the id prop matches between GestureTrigger and GestureViewer components for the animation to work properly. (default value: default)
 
Handling Dismissal Animations
onDismissStart Callback
The onDismissStart callback is triggered immediately when the dismiss animation begins, which is useful for hiding UI elements that should disappear before the animation completes.
function App() {
  const [visible, setVisible] = useState(false);
  const [showExternalUI, setShowExternalUI] = useState(false); 
  const handleDismissStart = () => {
    setShowExternalUI(false);
  };
  return (
    <Modal visible={visible} transparent>
      <GestureViewer
        onDismissStart={handleDismissStart} 
        {...modalProps}
      />
      {showExternalUI && (
        <View>
          <Text>{`${currentIndex + 1} / ${totalCount}`}</Text>
        </View>
      )}
    </Modal>
  );
}
 
Dismissing from Custom Components
You can dismiss the viewer programmatically using the dismiss helper from renderContainer:
<GestureViewer
  renderContainer={(children, { dismiss }) => ( 
    <View>
      {children}
      <Pressable
        {/* 
          This button will properly trigger the dismiss animation
          back to the original thumbnail position 
        */}
        onPress={dismiss} 
      >
        <Text>Close</Text>
      </Pressable>
    </View>
  )}
/>
 
Why Use renderContainer's dismiss?
When using trigger-based animations, it's important to use the dismiss method provided by renderContainer instead of directly controlling the visibility with setVisible(false). Here's why:
// ❌ Avoid: This will close immediately without trigger animation
<Button onPress={() => setVisible(false)} title="Close" />
// ✅ Preferred: This will use the trigger animation to close
<Button onPress={dismiss} title="Close" />
How It Works:
- With Trigger Animation:
- When 
dismiss is called, the viewer will animate back to the original trigger position 
onDismissStart is called at the start of the animation 
onDismiss is called after the animation completes 
 
- Without Trigger Animation:
- If no trigger animation is configured, 
dismiss will still work as a simple close 
 
 
Complete Example with Dismiss Handling
function ImageViewer() {
  const [visible, setVisible] = useState(false);
  const [showUI, setShowUI] = useState(true);
  return (
    <Modal visible={visible} transparent>
      <GestureViewer
        onDismissStart={() => setShowUI(false)}
        onDismiss={() => setVisible(false)}
        renderContainer={(children, { dismiss }) => (
          <View>
            {children}
            {showUI && (
              <View>
                <Button onPress={dismiss} title="Close" />
              </View>
            )}
          </View>
        )}
      />
    </Modal>
  );
}
 
Dismiss Behavior with Trigger Animation
When using trigger-based animations, the dismiss animation will animate back to the original trigger position. The onDismissStart callback is called at the start of this animation, allowing you to hide any UI elements that should not be visible during the dismiss animation.
<GestureViewer
  onDismissStart={() => {
    console.log('Dismiss animation started');
    setShowUI(false);
  }}
  onDismiss={() => {
    console.log('Dismiss animation complete');
    setVisible(false);
  }}
/>
 
This pattern ensures a smooth user experience by:
- Immediately hiding UI elements when dismiss starts
 
- Allowing the dismiss animation to complete naturally
 
- Cleaning up any resources only after the animation is fully complete
 
Best Practices
- Always use the 
dismiss method from renderContainer when you want to close the viewer with animations 
- Use 
onDismissStart to hide UI elements that shouldn't be visible during the dismiss animation 
- Use 
onDismiss for cleanup operations that should happen after the animation completes 
Common Pitfalls
// ❌ Avoid: This will bypass the trigger animation
<Button onPress={() => setVisible(false)} title="Close" />
// ❌ Avoid: This will cause the animation to break
const handleClose = () => {
  setShowUI(false);
  setVisible(false); // Closes too early
};
<Button onPress={handleClose} title="Close" />
// ✅ Correct: Let the animation complete naturally
<GestureViewer
  onDismissStart={() => setShowUI(false)}
  onDismiss={() => setVisible(false)}
  renderContainer={(children, { dismiss }) => (
    <View>
      {children}
      {showUI && (
        <View>
          <Button onPress={dismiss} title="Close" />
        </View>
      )}
    </View>
  )}
/>
 
This pattern ensures that your trigger-based animations work consistently and provides the best user experience.
API Reference
GestureTrigger Props
interface GestureTriggerProps<T extends WithOnPress> {
  id?: string; // Identifier to associate with GestureViewer (default: "default")
  children: ReactElement<T>; // Single pressable child element
  onPress?: (...args: unknown[]) => void; // Additional onPress handler
}
 
TriggerAnimation Config
interface TriggerAnimationConfig {
  duration?: number; // Animation duration in milliseconds
  easing?: EasingFunction; // Custom easing function
  reduceMotion?: boolean; // Respect system reduce motion preference
  onAnimationComplete?: () => void; // Callback fired when animation completes
}
 
| Property | Type | Default | Description | 
|---|
duration | number | 300 | Animation duration in milliseconds | 
easing | EasingFunction | Easing.bezier(0.25, 0.1, 0.25, 1.0) | Easing function for the animation | 
reduceMotion | ReduceMotion | undefined | Whether to respect system reduce motion settings | 
onAnimationComplete | () => void | undefined | Callback fired when the animation completes | 
NOTE
The trigger animation works by measuring the trigger element's position when pressed and animating the modal from that position to full screen.