01srcalt
# Basic usage
Point src at an image and the thumbnail becomes a full viewer: click it to open, scroll or double-click to zoom, pinch on touch, drag to pan. Escape or a backdrop click closes it again.

import { HanaImgViewer } from 'hana-img-viewer-react'
export default function Example() {
return (
<HanaImgViewer
src="https://pixiv-r2.caelum.moe/121909597.png"
alt="Artwork 121909597"
/>
)
}02previewSrc
# Progressive quality
Keep src light and pass the full-quality file as previewSrc. The overlay opens instantly with the thumbnail, then swaps in the high-res image the moment it finishes loading — no spinner, no layout shift.

import { HanaImgViewer } from 'hana-img-viewer-react'
const thumb = '/covers/summer-320w.jpg'
const fullRes = '/covers/summer-2400w.png'
export default function Example() {
return <HanaImgViewer src={thumb} previewSrc={fullRes} alt="Summer cover" />
}03openonOpenChange
# Controlled state
Pass open to drive visibility from outside. The viewer never flips state on its own — it calls onOpenChange and waits for your state to change, so your state stays the single source of truth. Controlled mode is decided at mount.

import { HanaImgViewer } from 'hana-img-viewer-react'
import { useState } from 'react'
export default function Example() {
const [open, setOpen] = useState(false)
return (
<>
<button type="button" onClick={() => setOpen(true)}>Open from the outside</button>
<HanaImgViewer open={open} onOpenChange={setOpen} src="/photos/garden.png" alt="Garden" />
<p>open: {String(open)}</p>
</>
)
}05container
# Custom container
Mount the overlay anywhere by passing container. Here it renders inside a scrollable panel in a dialog. While the ref is still null an open request simply waits — the viewer resolves it as soon as the container exists.
import { HanaImgViewer } from 'hana-img-viewer-react'
import { useState } from 'react'
export default function Example() {
const [container, setContainer] = useState<HTMLElement | null>(null)
return (
<section className="scrollable-panel">
<div ref={setContainer} />
<HanaImgViewer
container={container}
closeOnEscape={false}
src="/photos/artwork.png"
alt="Artwork"
/>
</section>
)
}06minZoommaxZoom
# Zoom control
Zoom is always on. Clamp the wheel, pinch, and double-click range with minZoom and maxZoom.

default: 0.5x · 10x
minZoom: 1 · maxZoom: 3{/* Default 0.5x-10x zoom */}
<HanaImgViewer src="/covers/summer.jpg" alt="Cover" />
{/* Wheel, pinch, and double-click clamped to 1x-3x */}
<HanaImgViewer minZoom={1} maxZoom={3} src="/photos/garden.png" alt="Garden" />07closeOnBackdropClickcloseOnEscapeshowCloseButton
# Close behavior
All three escape hatches are opt-out: backdrop click, Escape, and the corner showCloseButton. The left viewer ignores backdrop clicks; the middle one ignores Escape; the right one hides the close button.

closeOnBackdropClick: false
closeOnEscape: false
showCloseButton: false{/* Backdrop clicks are ignored; press Escape or the close button */}
<HanaImgViewer closeOnBackdropClick={false} src="/a.png" alt="A" />
{/* Escape is ignored; click the backdrop or the close button */}
<HanaImgViewer closeOnEscape={false} src="/b.png" alt="B" />
{/* No close button; backdrop and Escape still close */}
<HanaImgViewer showCloseButton={false} src="/c.png" alt="C" />08transitionDuration
# Transition duration
The open and close FLIP runs for transitionDuration milliseconds. The left one keeps the default 300 ms; the right one stretches the animation to 600 ms.

default: 300ms
transitionDuration: 600{/* Default 300ms open/close FLIP */}
<HanaImgViewer src="/covers/summer.jpg" alt="Cover" />
{/* Longer, smoother open/close animation */}
<HanaImgViewer transitionDuration={600} src="/photos/garden.png" alt="Garden" />