Skip to main content

Overview

The useZoomImageWheel composable provides wheel-based image zoom functionality with support for mouse wheel, pinch-to-zoom gestures, and programmatic zoom controls. It enables users to zoom and pan images using scroll/wheel events and touch gestures.

Signature

function useZoomImageWheel(): {
  createZoomImage: (
    container: HTMLElement,
    options?: ZoomImageWheelOptions
  ) => void
  zoomImageState: ZoomImageWheelState
  setZoomImageState: (state: ZoomImageWheelStateUpdate) => void
}

Return Value

createZoomImage
function
required
Initializes the zoom functionality on the specified container element.
zoomImageState
ZoomImageWheelState
required
Reactive state object containing current zoom properties
currentZoom
number
Current zoom level (1 = 100%, 2 = 200%, etc.)
enable
boolean
Whether zoom functionality is enabled
currentPositionX
number
Current X-axis position of the zoomed image in pixels
currentPositionY
number
Current Y-axis position of the zoomed image in pixels
currentRotation
number
Current rotation angle in degrees
setZoomImageState
function
required
Updates the zoom state programmatically

Examples

Basic Usage

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useZoomImageWheel } from '@zoom-image/vue'

const imageContainerRef = ref<HTMLDivElement>()
const { createZoomImage, zoomImageState } = useZoomImageWheel()

onMounted(() => {
  if (imageContainerRef.value) {
    createZoomImage(imageContainerRef.value)
  }
})
</script>

<template>
  <div>
    <p>Current zoom: {{ Math.round(zoomImageState.currentZoom * 100) }}%</p>
    <div ref="imageContainerRef" class="h-[300px] w-[200px] cursor-crosshair">
      <img src="/sample.jpg" alt="Zoomable image" class="h-full w-full" />
    </div>
  </div>
</template>

With Custom Options

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useZoomImageWheel } from '@zoom-image/vue'

const imageContainerRef = ref<HTMLDivElement>()
const { createZoomImage, zoomImageState } = useZoomImageWheel()

onMounted(() => {
  if (imageContainerRef.value) {
    createZoomImage(imageContainerRef.value, {
      maxZoom: 6,
      wheelZoomRatio: 0.2,
      initialState: {
        currentZoom: 1.5,
        currentRotation: 0
      }
    })
  }
})
</script>

<template>
  <div ref="imageContainerRef" class="h-[300px] w-[200px]">
    <img src="/sample.jpg" alt="Zoomable image" class="h-full w-full" />
  </div>
</template>

Programmatic Controls

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useZoomImageWheel } from '@zoom-image/vue'

const imageContainerRef = ref<HTMLDivElement>()
const { 
  createZoomImage, 
  zoomImageState, 
  setZoomImageState 
} = useZoomImageWheel()

const zoomIn = () => {
  setZoomImageState({
    currentZoom: zoomImageState.currentZoom + 0.5
  })
}

const zoomOut = () => {
  setZoomImageState({
    currentZoom: zoomImageState.currentZoom - 0.5
  })
}

const rotate = () => {
  setZoomImageState({
    currentRotation: zoomImageState.currentRotation + 90
  })
}

onMounted(() => {
  if (imageContainerRef.value) {
    createZoomImage(imageContainerRef.value)
  }
})
</script>

<template>
  <div>
    <div class="flex space-x-2 mb-4">
      <button @click="zoomIn" class="px-4 py-2 bg-gray-100 rounded">
        Zoom In
      </button>
      <button @click="zoomOut" class="px-4 py-2 bg-gray-100 rounded">
        Zoom Out
      </button>
      <button @click="rotate" class="px-4 py-2 bg-gray-100 rounded">
        Rotate 90°
      </button>
    </div>
    
    <div ref="imageContainerRef" class="h-[300px] w-[200px] cursor-crosshair">
      <img src="/sample.jpg" alt="Zoomable image" class="h-full w-full" />
    </div>
  </div>
</template>

With Image Cropping

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useZoomImageWheel } from '@zoom-image/vue'
import { cropImage } from '@zoom-image/core'

const imageContainerRef = ref<HTMLDivElement>()
const croppedImage = ref<string>()
const { createZoomImage, zoomImageState } = useZoomImageWheel()

const handleCrop = async () => {
  if (!imageContainerRef.value) return
  
  croppedImage.value = await cropImage({
    currentZoom: zoomImageState.currentZoom,
    image: imageContainerRef.value.querySelector('img') as HTMLImageElement,
    positionX: zoomImageState.currentPositionX,
    positionY: zoomImageState.currentPositionY,
    rotation: zoomImageState.currentRotation
  })
}

onMounted(() => {
  if (imageContainerRef.value) {
    createZoomImage(imageContainerRef.value)
  }
})
</script>

<template>
  <div>
    <div class="flex items-center gap-4">
      <div ref="imageContainerRef" class="h-[300px] w-[200px] cursor-crosshair">
        <img src="/sample.jpg" alt="Zoomable image" class="h-full w-full" />
      </div>
      
      <img 
        v-if="croppedImage" 
        :src="croppedImage" 
        alt="Cropped result" 
        class="h-[300px] w-[200px]"
      />
    </div>
    
    <button @click="handleCrop" class="mt-4 px-4 py-2 bg-gray-100 rounded">
      Crop Image
    </button>
  </div>
</template>

Cleanup

The composable automatically cleans up event listeners and resources when the component is unmounted using Vue’s onUnmounted hook. If you need to reinitialize the zoom on the same container, simply call createZoomImage again - it will automatically clean up the previous instance.
<script setup lang="ts">
import { ref } from 'vue'
import { useZoomImageWheel } from '@zoom-image/vue'

const imageContainerRef = ref<HTMLDivElement>()
const { createZoomImage } = useZoomImageWheel()

const reinitialize = () => {
  if (imageContainerRef.value) {
    // Previous instance is automatically cleaned up
    createZoomImage(imageContainerRef.value, {
      maxZoom: 8
    })
  }
}
</script>

See Also

Build docs developers (and LLMs) love