npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-carousel-zoom

v1.2.4

Published

Responsive image/video carousel with tap-to-zoom, swipe, thumbnails, and analytics hooks

Readme

react-carousel-zoom

Responsive React image/video carousel with tap-to-zoom, swipe navigation, thumbnails, video support, optional fullscreen overlay, and analytics hooks.

Features

  • Images & video — mixed slides with posters, autoplay rules, and resume playback on slide return
  • Thumbnails — square thumb strip with auto-scroll; video slides show a play badge
  • Swipe & navigation — touch/pointer swipe, prev/next buttons, keyboard arrows
  • Tap-to-zoom — zoom at tap point (2x/3x), pan when zoomed, second tap resets
  • Fullscreen overlay — optional first-tap fullscreen on images (separate desktop/mobile control)
  • Sizing — fixed height, aspect ratio, or adaptive height per active slide
  • Responsive images — native srcSet / sizes; lazy-load off-screen slides
  • Auto slide — timed advance with pause on hover, zoom, drag, or fullscreen
  • Analytics — callback for taps, thumbs, nav, swipe, zoom, auto-slide, and fullscreen
  • Accessible — keyboard nav, tab panels, live region announcements

Install

npm install react-carousel-zoom

Peer dependencies: react and react-dom (v17+).

Styles (required):

import 'react-carousel-zoom/styles.css';

Next.js App Router: the package ships with a "use client" banner — import CarouselZoom in a Client Component.

Quick start

import { CarouselZoom, type MediaItem } from 'react-carousel-zoom';
import 'react-carousel-zoom/styles.css';

const items: MediaItem[] = [
  {
    id: '1',
    type: 'image',
    src: '/images/product-large.jpg',
    srcSet: '/images/product-400.jpg 400w, /images/product-800.jpg 800w',
    sizes: '(max-width: 768px) 100vw, 50vw',
    alt: 'Product front view',
  },
  {
    id: '2',
    type: 'video',
    src: '/images/video-poster.jpg',
    poster: '/images/video-poster.jpg',
    videoSrc: '/videos/product.mp4',
    aspectRatio: 16 / 9,
  },
];

export function ProductGallery() {
  return (
    <CarouselZoom
      items={items}
      loop
      mediaFit="cover"
      aspectRatio={1}
      zoomScale={2}
      onAnalytics={(event) => console.log(event.type, event.item.id)}
    />
  );
}

Product page (PDP) example

Typical setup: cover inline, fullscreen on mobile tap, white fullscreen background, contain in fullscreen (automatic).

<CarouselZoom
  items={items}
  activeIndex={index}
  onIndexChange={setIndex}
  mediaFit="cover"
  aspectRatio={1}
  size={{ width: '100%' }}
  fullscreenOnImageClick={{ desktop: false, mobile: true }}
  mobileMaxWidth={768}
  fullscreenBackground="#ffffff"
  showThumbnails
  showNavigation
  loop
/>

Exports

| Export | Description | |--------|-------------| | CarouselZoom | Main component | | DEFAULT_MOBILE_MAX_WIDTH | Default mobile breakpoint (768) | | MediaItem, CarouselZoomProps, FullscreenOnImageClick, … | TypeScript types |

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | items | MediaItem[] | required | Ordered slides (images and videos) | | showThumbnails | boolean | true | Thumbnail strip below carousel | | showNavigation | boolean | true | Prev/next arrow buttons | | loop | boolean | false | Infinite loop between first and last slide | | size | CarouselSize | — | Viewport width / height (e.g. { height: 400 }) | | adaptiveHeight | boolean | false | Resize viewport to active slide aspect ratio | | aspectRatio | number | 4/3 | Fixed viewport ratio when height is not set | | zoomScale | number | 2 | Zoom multiplier (clamped to ≥ 1) | | zoomOnHover | boolean | false | Zoom on mouse hover on desktop (tap-to-zoom disabled while enabled) | | defaultIndex | number | 0 | Initial slide (uncontrolled mode) | | activeIndex | number | — | Controlled active slide index | | onIndexChange | (index) => void | — | Called when the active slide changes | | onAnalytics | (event) => void | — | Analytics / tracking callback | | thumbnailSize | number | 64 | Thumbnail size in px | | swipeThreshold | number | 50 | Min horizontal swipe distance in px | | autoSlide | boolean | false | Automatically advance slides | | autoSlideInterval | number | 4000 | Auto-slide interval in ms | | pauseAutoSlideOnHover | boolean | true | Pause auto-slide while hovered | | videoAutoPlay | boolean | true | Autoplay video on user navigation only | | videoMuted | boolean | true | Mute video (helps autoplay) | | videoLoop | boolean | false | Loop video while slide is active | | videoControls | boolean | true | Show native video controls | | mediaFit | 'cover' \| 'contain' | 'cover' | How media fills the inline slide | | className | string | — | Root class name (inline mode only) | | ariaLabel | string | 'Image and video carousel' | Accessible region label | | fullscreenOnImageClick | boolean \| { desktop?, mobile? } | off | First image tap opens fullscreen overlay | | mobileMaxWidth | number | 768 | Viewport width (px) at or below = mobile | | fullscreenBackground | string | #000 | Fullscreen overlay background color |

MediaItem

| Field | Type | Description | |-------|------|-------------| | id | string | Unique identifier | | type | 'image' \| 'video' | Slide type | | src | string | Image URL (fallback / video poster) | | srcSet | string | Responsive srcset for images | | sizes | string | sizes attribute for images | | alt | string | Alt text | | videoSrc | string | Video URL (required for type: 'video') | | poster | string | Video poster frame | | videoLoop | boolean | Per-slide loop override | | videoControls | boolean | Per-slide controls override | | thumbnailSrc | string | Custom thumbnail image | | aspectRatio | number | Width÷height before media loads (e.g. 16/9) |

Slide order always follows the items array.

Sizing

Viewport size is resolved in this order:

  1. size.height set — fixed height; aspectRatio is ignored
  2. adaptiveHeight — height follows the active slide’s aspect ratio
  3. aspectRatio — fixed ratio (e.g. 1 = square, 16/9 = widescreen)

Do not pass size.height if you want aspectRatio to control the shape:

<CarouselZoom items={items} aspectRatio={1} size={{ width: '100%' }} />

Zoom

  1. First tap on an image zooms to zoomScale, centered on the tap point
  2. While zoomed, drag to pan within bounds
  3. Second tap resets to 1×

On desktop, set zoomOnHover={true} to zoom while the cursor is over the image (the view follows the cursor). Zoom resets on mouse leave. Tap-to-zoom is disabled while hover zoom is enabled.

Swipe is disabled while zoomed. In fullscreen, thumbnails, arrows, and the close button hide while zoomed.

Fullscreen overlay

Enable per platform:

// Mobile only (common PDP pattern)
fullscreenOnImageClick={{ desktop: false, mobile: true }}

// Both platforms
fullscreenOnImageClick={true}

| Behavior | Detail | |----------|--------| | Trigger | First tap on a main image (not video) when enabled for current device | | Overlay | Portaled to document.body, edge-to-edge, max z-index | | mediaFit | Always contain in fullscreen; inline uses your mediaFit prop | | Background | fullscreenBackground prop (default #000) | | Close | X button, Escape, or browser back | | Zoom in fullscreen | Tap-to-zoom works; chrome hides while zoomed |

Mobile vs desktop is determined by mobileMaxWidth (default 768px).

Video

  • Autoplay when reached via user navigation (thumbs, arrows, swipe) if videoAutoPlay is true
  • Auto-slide does not trigger video autoplay
  • Playback position is preserved when leaving and returning to a slide
  • When videoControls is false, tap the video area to play/pause
  • Missing videoSrc shows a fallback UI (dev console warning in development)

Analytics events

| type | When fired | |--------|------------| | main_click | Tap on main image | | thumb_click | Thumbnail selected | | nav_prev / nav_next | Arrow button used | | swipe | Horizontal swipe (meta.direction: 'left' | 'right') | | zoom_in / zoom_out | Image zoom toggled | | auto_slide | Slide advanced by timer | | fullscreen_open / fullscreen_close | Fullscreen overlay opened or closed |

Accessibility

  • Keyboard: focus viewport → ←/→ change slides, Home/End jump to ends
  • Tabs: thumbnails linked to slide panels via aria-controls
  • Live region: slide changes are announced

Development

git clone <your-repo>
cd react-carousel-zoom
npm install
npm run dev        # demo at http://localhost:5173
npm run build      # build dist/ for npm
npm run typecheck

Only dist/ and LICENSE are published to npm. The demo/ app is for local development.

Publish

npm login
npm run build
npm publish

License

MIT © Om Vaishnav