Skip to content
Containment

Image Viewer

Image viewer — tap a picture and it lifts off the page into a full-screen, zoomable, swipeable lightbox, then settles back where it came from. The feel of react-photo-view, for Svelte.

Walkthrough

2. Controlled

Hand it images and drive open and index yourself when the trigger is not a thumbnail — a "view all" button, a keyboard shortcut, a route.

Showing #1

<script lang="ts">	import { Button, ImageViewer } from 'omaris';​	let open = $state(false);	let index = $state(0);​	const IMAGES = [		{ src: '/docs/photos/najaf.svg', alt: 'Najaf' },		{ src: '/docs/photos/kirkuk.svg', alt: 'Kirkuk' },		{ src: '/docs/photos/erbil.svg', alt: 'Erbil' }	];</script>​<div class="flex flex-col items-center gap-3">	<Button		variant="tonal"		onclick={() => {			index = 0;			open = true;		}}	>		View all 3 photos	</Button>	<p class="text-body-sm text-muted-foreground">Showing #{index + 1}</p></div>​<ImageViewer images={IMAGES} bind:open bind:index />

3. Shapes

Every picture is fitted to the screen on its own terms: a panorama fills the width, a portrait the height, and one smaller than the screen stays its own size. Zoomed in, a picture pans to its edge before the next one slides in.

<script lang="ts">	import { ImageViewer, ImageViewerItem } from 'omaris';​	/** A gradient landscape of any size, so the shapes are real. */	function picture(width: number, height: number, hue: number) {		const svg = `<svg xmlns='http://www.w3.org/2000/svg' width='${width}' height='${height}' viewBox='0 0 ${width} ${height}'>			<defs><linearGradient id='g' x1='0' y1='0' x2='1' y2='1'>				<stop offset='0' stop-color='hsl(${hue} 70% 58%)'/>				<stop offset='1' stop-color='hsl(${hue + 50} 65% 42%)'/>			</linearGradient></defs>			<rect width='100%' height='100%' fill='url(#g)'/>			<circle cx='${width * 0.25}' cy='${height * 0.3}' r='${Math.min(width, height) * 0.12}' fill='white' fill-opacity='0.35'/>			<path d='M0 ${height * 0.75} L${width * 0.3} ${height * 0.5} L${width * 0.55} ${height * 0.7} L${width * 0.8} ${height * 0.45} L${width} ${height * 0.65} V${height} H0Z' fill='black' fill-opacity='0.18'/>		</svg>`;		return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;	}​	const SHAPES = [		{ alt: 'Panorama', src: picture(3000, 900, 200) },		{ alt: 'Portrait', src: picture(900, 1600, 330) },		{ alt: 'Square', src: picture(1400, 1400, 30) },		{ alt: 'Small original', src: picture(320, 240, 140) }	];</script>​<ImageViewer>	<div class="grid w-full grid-cols-2 gap-3 sm:grid-cols-4">		{#each SHAPES as shape (shape.alt)}			<ImageViewerItem src={shape.src} alt={shape.alt} class="overflow-hidden rounded-shape-md">				<img src={shape.src} alt={shape.alt} class="aspect-square w-full object-cover" />			</ImageViewerItem>		{/each}	</div></ImageViewer>

4. Overridden

Every default is a prop. loop lets the arrow keys and buttons wrap from the last picture to the first (a swipe still stops at the end), maxScale caps the zoom, dismissible={false} keeps a pull or a tap on the black from closing it, and actions adds to the toolbar.

<script lang="ts">	import { ImageViewer, ImageViewerItem } from 'omaris';​	const PHOTOS = [		{ name: 'najaf', alt: 'Najaf' },		{ name: 'kirkuk', alt: 'Kirkuk' },		{ name: 'erbil', alt: 'Erbil citadel' }	];</script>​<ImageViewer loop maxScale={2} dismissible={false}>	{#snippet actions({ image })}		<a			href={image.src}			target="_blank"			rel="noreferrer"			class="grid size-10 place-items-center rounded-full transition-colors duration-200 ease-standard hover:bg-white/15 focus-visible:ring-2 focus-visible:ring-white/70 focus-visible:outline-none motion-reduce:transition-none"			aria-label="Open the original"		>			<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="size-5">				<path					d="M14 4h6v6M20 4l-9 9M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"					stroke-linecap="round"					stroke-linejoin="round"				/>			</svg>		</a>	{/snippet}	<div class="grid w-full grid-cols-3 gap-3">		{#each PHOTOS as photo (photo.name)}			<ImageViewerItem				src="/docs/photos/{photo.name}.svg"				alt={photo.alt}				caption={photo.alt}				class="overflow-hidden rounded-shape-lg"			>				<img					src="/docs/photos/{photo.name}.svg"					alt={photo.alt}					class="aspect-square w-full object-cover"				/>			</ImageViewerItem>		{/each}	</div></ImageViewer>