GitHubX

Image Viewer

PreviousNext

Zoom and pan capabilities for images with touch support

Add zoom and pan capabilities to images. Builds on the base Image component with click-to-zoom, cursor-following zoom, touch gestures (pinch, pan, double-tap), and optional controls.

      
      <script setup lang="ts">
import { ImageFallback } from "@/components/image";
import { ImageViewerContainer, ImageViewerProvider, ImageViewerSource, ImageViewerZoomInControl, ImageViewerZoomOutControl } from "@/components/image-viewer";
</script>

<template>
  <ImageViewerProvider :scale="1" :max-scale="3" :step="2" :reset-on-click-outside="false">
    <ImageViewerContainer class="relative">
      <ImageViewerSource class="w-96 aspect-video" src="https://picsum.photos/id/229/600/400" alt="Zoomable image" />
      <ImageFallback as-child>
        <div class="w-96 aspect-video p-2 text-center text-muted-foreground bg-muted">
          Loading...
        </div>
      </ImageFallback>

      <div class="hidden md:flex p-1 bg-background/75 rounded-lg absolute bottom-2 right-2 pointer-events-auto backdrop-blur-lg">
        <ImageViewerZoomInControl variant="ghost" />
        <ImageViewerZoomOutControl variant="ghost" />
      </div>
    </ImageViewerContainer>
  </ImageViewerProvider>
</template>
    

Features

  • Click to zoom — Zoom in/out by clicking on the image (desktop)
  • Cursor following — Zoom centers on cursor position for intuitive navigation
  • Touch support — Pinch to zoom, pan when zoomed, double-tap to toggle max zoom
  • Zoom controls — Buttons, slider, and minimap for precise control
  • Composable — Use with Image component for loading states

Installation

Install from the Vuzeno registry. Requires the Image component:

      
      bunx --bun shadcn-vue@latest add https://vuzeno.com/r/image-viewer.json
    

Examples

Basic

Wrap your image with ImageViewerProvider and ImageViewerContainer, then use ImageViewerSource:

      
      <template>
  <ImageViewerProvider :scale="1" :max-scale="6">
    <ImageViewerContainer>
      <ImageViewerSource src="..." alt="..." />
      <ImageFallback>
        <div class="p-2 text-center text-muted-foreground bg-muted">Loading...</div>
      </ImageFallback>
    </ImageViewerContainer>
  </ImageViewerProvider>
</template>
    

With Controls

      
      <script setup lang="ts">
import { ImageFallback } from "@/components/image";
import { ImageViewerContainer, ImageViewerProvider, ImageViewerSource, ImageViewerZoomInControl, ImageViewerZoomMap, ImageViewerZoomOutControl, ImageViewerZoomSlider } from "@/components/image-viewer";
</script>

<template>
  <ImageViewerProvider class="flex flex-col gap-4" :scale="1" :max-scale="6" :step="2" :reset-on-click-outside="false">
    <div class="flex gap-2">
      <div class="hidden md:flex flex-col gap-4"></div>
      <ImageViewerContainer class="relative">
        <ImageViewerSource src="https://picsum.photos/id/229/600/400" alt="Zoomable image" />
        <ImageFallback>
          <div class="p-2 text-center text-muted-foreground bg-muted">
            Loading...
          </div>
        </ImageFallback>

        <div class="hidden md:flex p-1 bg-background/75 rounded-lg absolute bottom-2 right-2 pointer-events-auto backdrop-blur-lg">
          <ImageViewerZoomInControl variant="ghost" />
          <ImageViewerZoomOutControl variant="ghost" />
        </div>
      </ImageViewerContainer>

      <ImageViewerZoomSlider orientation="vertical" class="data-[orientation=vertical]:h-auto flex-none" />
    </div>

    <ImageViewerZoomMap />
  </ImageViewerProvider>
</template>
    

Add zoom controls and minimap for full control:

      
      <template>
  <ImageViewerProvider :scale="1" :max-scale="6">
    <ImageViewerContainer>
      <ImageViewerSource src="..." alt="..." />
      <div class="absolute bottom-2 right-2 flex gap-2">
        <ImageViewerZoomInControl />
        <ImageViewerZoomOutControl />
      </div>
    </ImageViewerContainer>
    <ImageViewerZoomSlider orientation="vertical" />
    <ImageViewerZoomMap />
  </ImageViewerProvider>
</template>
    

Cursor Following

The followCursor prop controls how the zoomed image is positioned:

ModeBehavior
followCursor: true (default)The zoom focuses on the cursor position. As you move the mouse, the visible area shifts to keep the cursor point centered.
followCursor: falseThe zoom is centered on the image. The visible area remains fixed at the center regardless of cursor position.

When using ImageViewerZoomSlider, the component automatically disables cursor following during drag operations to prevent visual conflicts.

Touch Gestures

On touch devices:

  • Pinch — Two-finger pinch to zoom in/out
  • Pan — Single-finger drag when zoomed to pan the image
  • Double-tap — Toggle between max zoom and reset