Skip to content
Pinky UI

mobile

Pinch Zoom Image

Two-finger pinch to zoom, one-finger pan once zoomed, and a vertical swipe-to-dismiss at rest scale — the standard photo-viewer gesture set.

  • mobile
  • pinch
  • zoom
  • image
  • dismiss
Related pattern

Built on Pointer Events directly, not a gesture library; distinct from Swipe Media Inspector's single-axis scrub.

Live preview

Quick usage

usage
import { PinchZoomImage } from "@pinky-ui/systems";

<PinchZoomImage src={photo} alt="Product detail" onDismiss={close} />

Install

Add @pinky-ui/systems as a dependency, or use the CLI to copy this component's source directly into your project — no dependency to manage, fully editable.

shell
npm install @pinky-ui/systems

Prefer to run the whole repository locally instead?

repository
git clone https://github.com/florash/Pinky-UI.git
cd Pinky-UI
npm install
npm run dev

Accessibility

  • Every gesture has a labelled button or keyboard path, and primary controls meet a 44px touch target.
  • State remains readable without relying on colour or motion.

Performance

  • Interaction state is local and bounded; gesture updates do not require a page-wide render loop.
  • Timers and listeners are cleaned up on unmount.

Reduced motion

The same state change resolves immediately; travel, scale and spring motion are removed without removing the interaction.

When to use

  • Touch-first product surfaces where the next action should stay close to the hand.

When not to use

  • Desktop-only workflows or interactions whose gesture would hide the only available action.

Skill

Purpose

The standard mobile photo-viewer gesture set: two-finger pinch to zoom, one-finger pan once zoomed, and a vertical swipe down at rest scale to dismiss — built on raw Pointer Events, not a gesture library.

Use when

A single image deserves close inspection — a product photo, a document page, a full-screen media viewer opened from a gallery or lightbox.

Avoid

  • Thumbnails or any image still part of a scrollable collection — zoom only belongs to a surface the user has explicitly opened
  • Images with essential UI overlaid on them (buttons, captions users must reach) unless those controls stay fixed outside the zoomable layer
  • As the only way to see detail — pair with a real close button, not just the swipe-down gesture

Interaction

Pinch scales between 1x and maxScale. Above 1x, a single finger pans the image within its bounds. At exactly 1x, a single-finger vertical drag fades and translates the image, and releasing past ~110px calls onDismiss; short of that, it springs back on Jelly's elastic curve. Two-finger and one-finger gestures never fight each other because pan/dismiss only read from a single active pointer.

Accessibility

An explicit close button is always rendered when onDismiss is provided — never rely on the swipe gesture as the only way out. The group has a descriptive label stating both available gestures.

Reduced motion

The release settle (scale/position snap-back) resolves instantly; pinch and pan themselves are direct 1:1 pointer tracking either way, since they are driven by the user's own hand, not autonomous motion.

Performance

All tracking runs through Motion values (scale, x, y) — no React re-render happens during a pinch or pan, only on gesture start/end.

Composition and anti-patterns

Don't nest inside another draggable or swipeable surface (Swipe-to-Dismiss Card Sheet, Swipe Back Gesture) — pointer capture from one will fight the other for the same touch stream.

Open this skill on its own page