Prerendered at build time · hydrated on load

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.

Artwork 121909597
Click the artwork to open the viewer.
BasicUsage.vue
<script setup lang="ts">
import { HanaImgViewer } from 'hana-img-viewer'
</script>

<template>
  <HanaImgViewer
    src="https://pixiv-r2.caelum.moe/121909597.png"
    alt="Artwork 121909597"
  />
</template>

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.

Cover that upgrades to a high-res artwork
Open it and watch the image sharpen in place.
ProgressiveQuality.vue
<script setup lang="ts">
import { HanaImgViewer } from 'hana-img-viewer'

const thumb = '/covers/summer-320w.jpg'
const fullRes = '/covers/summer-2400w.png'
</script>

<template>
  <HanaImgViewer :src="thumb" :preview-src="fullRes" alt="Summer cover" />
</template>

03v-model:openupdate:open

# Controlled state

Bind v-model:open to drive visibility from outside. The viewer never flips state on its own — it emits update:open and waits for your state to change, so your ref stays the single source of truth.

Artwork 129115891
open: false
ControlledState.vue
<script setup lang="ts">
import { HanaImgViewer } from 'hana-img-viewer'
import { ref } from 'vue'

const open = ref(false)
</script>

<template>
  <button type="button" @click="open = true">Open from the outside</button>
  <HanaImgViewer v-model:open="open" src="/photos/garden.png" alt="Garden" />
  <p>open: {{ open }}</p>
</template>

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.

The overlay stays inside the dialog's scroll area.
CustomContainer.vue
<script setup lang="ts">
import { HanaImgViewer } from 'hana-img-viewer'
import { ref } from 'vue'

const container = ref<HTMLElement | null>(null)
</script>

<template>
  <section class="scrollable-panel">
    <div ref="container" />
    <HanaImgViewer
      :container="container"
      :close-on-escape="false"
      src="/photos/artwork.png"
      alt="Artwork"
    />
  </section>
</template>

06minZoommaxZoom

# Zoom control

Zoom is always on. Clamp the wheel, pinch, and double-click range with minZoom and maxZoom.

Default zoom rangedefault: 0.5x · 10xZoom clamped between 1x and 3xminZoom: 1 · maxZoom: 3
Open each one and try the scroll wheel or a double-click.
ZoomControl.vue
<template>
  <!-- Default 0.5x-10x zoom -->
  <HanaImgViewer src="/covers/summer.jpg" alt="Cover" />

  <!-- Wheel, pinch, and double-click clamped to 1x-3x -->
  <HanaImgViewer :min-zoom="1" :max-zoom="3" src="/photos/garden.png" alt="Garden" />
</template>

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.

Closes with Escape or the close buttoncloseOnBackdropClick: falseCloses with a backdrop click or the close buttoncloseOnEscape: falseCloses with the backdrop or Escape onlyshowCloseButton: false
Try the wrong close gesture first — nothing happens.
CloseBehavior.vue
<template>
  <!-- Backdrop clicks are ignored; press Escape or the close button -->
  <HanaImgViewer :close-on-backdrop-click="false" src="/a.png" alt="A" />

  <!-- Escape is ignored; click the backdrop or the close button -->
  <HanaImgViewer :close-on-escape="false" src="/b.png" alt="B" />

  <!-- No close button; backdrop and Escape still close -->
  <HanaImgViewer :show-close-button="false" src="/c.png" alt="C" />
</template>

08transitionDuration

# Transition duration

The open and close FLIP runs for transitionDuration milliseconds. The left viewer keeps the 300 ms default; the right one stretches the animation to 600 ms.

Default 300ms transitiondefault: 300ms600ms transitiontransitionDuration: 600
Open and close each one — the right viewer animates noticeably slower.
TransitionDuration.vue
<template>
  <!-- Default 300ms open/close FLIP -->
  <HanaImgViewer src="/covers/summer.jpg" alt="Cover" />

  <!-- Longer, smoother open/close animation -->
  <HanaImgViewer :transition-duration="600" src="/photos/garden.png" alt="Garden" />
</template>