Profile
Back to NewsBack
GitHub Trending 7 min
Reader Mode
nandorojo/galeria: The React (Native) Image Viewer. šŸ“·

nandorojo/galeria: The React (Native) Image Viewer. šŸ“·

8 hours ago

Galeria šŸ“·

An image viewer for React (+ Native). It works with any image component - bring your own image component (BYOICā„¢)

https://github.com/user-attachments/assets/5062e949-b205-4260-830c-38041cec26db

The video makes Web look a bit weird, but don't worry. Here is why – it's just because of FlashList's Masonry List, which wraps the views with additional cells breaking z-index. But it isn't a fundamental Galeria issue.

Features

  • Shared element transitions
  • Pinch to zoom
  • Double tap to zoom
  • Pan to close
  • Multi-image support
  • React Native Modal support
  • FlashList support
  • Clean API
  • Web support
  • Remote URLs & local images
  • New Architecture (Fabric) – required
  • Supports different images when collapsed and expanded
- This lets you show smaller thumbnails with higher resolution expanded images
  • Works with _any image component_
- from react-native - from solito/image - from next/image - from expo-image - on web - ...etc

For iOS and Android, the implementation uses Swift (ImageViewer.swift) and Kotlin (imageviewer) respectively – see credits.

Web support is a simplified version of the native experience powered by Framer Motion. It opens one photo or video at a time.

Resources

- "Don't be afraid to build a native library"

Usage

One Image

import { Galeria } from '@nandorojo/galeria'
import { Image } from 'react-native' // works with ANY image component!

const url = 'https://my-image.com/image.jpg'

export const SingleImage = ({ style }) => ( <Galeria urls={[url]}> <Galeria.Image> <Image source={{ uri: url }} style={style} /> </Galeria.Image> </Galeria> )

Multiple Images

Simply pass an array to urls.

import { Galeria } from '@nandorojo/galeria'
import { Image } from 'react-native' // works with ANY image component!

import localImage from './assets/local-image.png'

const urls = ['https://my-image.com/image.jpg', localImage]

export const MutliImage = ({ style }) => ( <Galeria urls={urls}> {urls.map((url, index) => ( <Galeria.Image index={index} key={...}> <Image source={typeof url === 'string' ? { uri: url } : url} style={style} /> </Galeria.Image> ))} </Galeria> )

Photos and Videos

iOS, Android, and web

Pass { type: 'video', source } entries alongside photos in urls. Existing image URLs and local image assets still work. A typed entry's source accepts a URL string, a bundled asset such as require('./video.mp4'), or { uri: string } for remote and local file URIs. Galeria.Item is an alias for Galeria.Image and accepts the same props.

import { Galeria, type MediaSource } from '@nandorojo/galeria'
import { Image } from 'react-native'

const media: MediaSource[] = [ { type: 'photo', source: 'https://example.com/photo.jpg' }, { type: 'video', source: 'https://example.com/video.mp4' }, ] const thumbnails = [ 'https://example.com/photo.jpg', 'https://example.com/video-poster.jpg', ]

export const MixedGallery = () => ( <Galeria urls={media} autoPlayVideo={false}> {thumbnails.map((uri, index) => ( <Galeria.Item index={index} key={uri}> <Image source={{ uri }} style={{ width: 120, height: 120 }} /> </Galeria.Item> ))} </Galeria> )

Videos open paused by default. Set autoPlayVideo to start playback when a video page becomes active. Playback uses native AVKit controls on iOS, Media3 controls on Android, and the browser's standard video controls on web. Tap the video to reveal the controls. Playback stops when dismissing the viewer. On iOS and Android, playback also pauses when leaving the page or putting the app in the background.

Provide your own thumbnail image, either from your server or generated in your app. Galeria reuses the loaded image from the mounted item as the video's poster and transition image. It does not extract video thumbnails. If the item is not mounted or its image has not loaded, the poster may be unavailable.

Web retains the single-item viewer and its existing close and scroll behavior. Browser autoplay policies still apply; if playback is blocked, use the play button. Web uses URL strings or { uri } sources directly, including bundled assets that your web bundler emits in those forms. Native numeric asset IDs and native file URIs must be resolved by the caller before passing them to web. Galeria reuses the caller's image URL as the poster.

Dark Mode

import { Galeria } from '@nandorojo/galeria'

export const DarkMode = () => ( <Galeria urls={urls} theme="dark"> ... </Galeria> )

FlashList

import { Galeria } from '@nandorojo/galeria'
import { Image, type ImageAssetSource } from 'react-native' // works with ANY image component!
import { FlashList } from '@shopify/flash-list'

import localImage from './assets/local-image.png'

const urls = ['https://my-image.com/image.jpg', localImage] const size = 100 export const FlashListSupport = () => { return ( <Galeria urls={urls}> <FlashList data={urls} renderItem={({ item, index }) => { // you should put this in a memoized component return ( <Galeria.Image index={index}> <Image source={src(item)} style={{ width: size, height: size }} /> </Galeria.Image> ) }} numColumns={3} estimatedItemSize={size} keyExtractor={(item, i) => item + i} /> </Galeria> ) }

const src = (s) => (typeof s === 'string' ? { uri: s } : s) // šŸ¤·ā€ā™‚ļø

Get Index of Currently Shown Image

iOS & Android

To get the index of the currently shown image in the image viewer use onIndexChange. It triggers on initial open of the image viewer and when the user scrolls through the images.

<Galeria urls={urls}>
  {urls.map((url, index) => (
     <Galeria.Image 
        index={index} key={...}  
        onIndexChange={(e) => setCurrentIndex(e.nativeEvent.currentIndex)}
        >
       <Image source={typeof url === 'string' ? { uri: url } : url} style={style} />
     </Galeria.Image>
   ))}
</Galeria>

Hide Blur Overlay

iOS only

Hide the blur overlay that appears behind the image viewer.

<Galeria.Image hideBlurOverlay>
  <Image source={{ uri: url }} style={style} />
</Galeria.Image>

Hide Page Indicators

iOS only

Hide the page indicator dots when viewing multiple images.

<Galeria.Image hidePageIndicators>
  <Image source={{ uri: url }} style={style} />
</Galeria.Image>

Plain Web Support

Galeria does not use _any_ React Native code on the web. It is a pure React component library.

So you can even use if you want to only use it on web.

import { Galeria } from '@nandorojo/galeria'

const urls = ['https://my-image.com/image.jpg']

export const WebSupport = () => ( <Galeria urls={urls}> <Galeria.Image> <img src={urls[0]} width={100} height={100} /> </Galeria.Image> </Galeria> )

Solito Image

import { SolitoImage } from 'solito/image'

const urls = ['https://my-image.com/image.jpg']

export const SolitoSupport = () => ( <Galeria urls={urls}> <Galeria.Image> <SolitoImage src={urls[0]} /> </Galeria.Image> </Galeria> )

Next.js Image

'use client'
import { Galeria } from '@nandorojo/galeria'
import Image from 'next/image'

const urls = ['https://my-image.com/image.jpg']

export const NextJS = () => ( <Galeria urls={urls}> <Galeria.Image> <Image src={urls[0]} width={100} height={100} // edit these props for your use case unoptimized /> </Galeria.Image> </Galeria> )

Expo Image

import { Galeria } from '@nandorojo/galeria'
import { Image } from 'expo-image'

const urls = ['https://my-image.com/image.jpg']

export const ExpoImage = () => ( <Galeria urls={urls}> <Galeria.Image> <Image source={urls[0]} style={{ width: 100, height: 100 }} /> </Galeria.Image> </Galeria> )

Installation

Requirements

  • New Architecture (Fabric) – Galeria v3.0+ requires the new architecture. This means Expo SDK 54+ or React Native 0.79+.
  • iOS 16.4+
For iOS 16.4+ deployment target: Expo SDK 56 requires iOS 16.4+ for ExpoModulesCore.
yarn add @nandorojo/galeria

or

npm i @nandorojo/galeria

Next.js / Solito

Add @nandorojo/galeria to transpilePackages in your next.config.js.

module.exports = {
  transpilePackages: ['@nandorojo/galeria'],
}

Expo

Galeria uses native libraries on iOS and Android, so it does not work with Expo Go. You will need to use a dev client.

After installing it, rebuild your native code:

npx expo prebuild
npx expo run:ios # or npx expo run:android

Credits

  • Under the hood, Galeria uses native libraries on iOS and Android.
  • On Web, Galeria uses Framer Motion.
  • Thanks to Luke Zhao for DynamicTransition
  • Thanks to Michael Henry for the iOS Image Viewer
  • Thanks to iielse for the Android Image Viewer
  • Thanks to Alan for building the Android integration.
Screenshot 2024-05-23 at 1 02 03 PM
Chat with me