Documentation Index
Fetch the complete documentation index at: https://mintlify.com/willnguyen1312/zoom-image/llms.txt
Use this file to discover all available pages before exploring further.
The Qwik adapter provides hooks for implementing zoom image functionality in Qwik applications with resumable, serializable state.
Installation
npm install @zoom-image/qwik
Available Hooks
The Qwik adapter exports four hooks that correspond to different zoom behaviors:
useZoomImageWheel - Zoom with mouse wheel
useZoomImageHover - Zoom on hover with separate zoom target
useZoomImageMove - Zoom on mouse move
useZoomImageClick - Zoom on click
useZoomImageWheel
Enables zooming with mouse wheel/trackpad scrolling.
API
function useZoomImageWheel(): {
createZoomImage: QRL<(...args: Parameters<typeof createZoomImageWheel>) => void>
zoomImageState: ZoomImageWheelState
setZoomImageState: QRL<(state: ZoomImageWheelStateUpdate) => void>
}
State
interface ZoomImageWheelState {
currentZoom: number
enable: boolean
currentPositionX: number
currentPositionY: number
currentRotation: number
}
Example
import { component$, useSignal, useVisibleTask$ } from "@builder.io/qwik"
import { useZoomImageWheel } from "@zoom-image/qwik"
export default component$(() => {
const imageContainerRef = useSignal<HTMLDivElement>()
const {
createZoomImage,
zoomImageState,
setZoomImageState
} = useZoomImageWheel()
useVisibleTask$(({ track }) => {
track(() => imageContainerRef.value)
if (imageContainerRef.value) {
createZoomImage(imageContainerRef.value)
}
})
return (
<div>
<p>Current zoom: {Math.round(zoomImageState.currentZoom * 100)}%</p>
<p>Scroll inside the image to zoom</p>
<div ref={imageContainerRef} class="h-[300px] w-[200px]">
<img src="/image.jpg" alt="Zoomable image" class="h-full w-full" />
</div>
<div class="flex gap-2">
<button
onClick$={() => {
setZoomImageState({
currentZoom: zoomImageState.currentZoom + 0.5
})
}}
>
Zoom In
</button>
<button
onClick$={() => {
setZoomImageState({
currentZoom: zoomImageState.currentZoom - 0.5
})
}}
>
Zoom Out
</button>
<button
onClick$={() => {
setZoomImageState({
currentRotation: zoomImageState.currentRotation + 90
})
}}
>
Rotate
</button>
</div>
</div>
)
})
useZoomImageHover
Displays a zoomed version in a separate container when hovering over the image.
API
function useZoomImageHover(): {
createZoomImage: QRL<(...args: Parameters<typeof createZoomImageHover>) => void>
zoomImageState: ZoomImageHoverState
setZoomImageState: QRL<(state: ZoomImageHoverStateUpdate) => void>
}
State
interface ZoomImageHoverState {
enabled: boolean
zoomedImgStatus: "idle" | "loading" | "loaded" | "error"
}
Example
import { component$, useSignal, useVisibleTask$ } from "@builder.io/qwik"
import { useZoomImageHover } from "@zoom-image/qwik"
export default component$(() => {
const imageContainerRef = useSignal<HTMLDivElement>()
const zoomTargetRef = useSignal<HTMLDivElement>()
const { createZoomImage, zoomImageState } = useZoomImageHover()
useVisibleTask$(({ track }) => {
track(() => imageContainerRef.value)
track(() => zoomTargetRef.value)
if (imageContainerRef.value && zoomTargetRef.value) {
createZoomImage(imageContainerRef.value, {
zoomImageSource: "/image-large.jpg",
customZoom: { width: 400, height: 600 },
zoomTarget: zoomTargetRef.value,
scale: 2
})
}
})
return (
<div class="flex gap-4">
<div ref={imageContainerRef} class="relative h-[300px] w-[200px]">
<img src="/image-small.jpg" alt="Hover to zoom" class="h-full w-full" />
</div>
<div ref={zoomTargetRef} class="absolute left-[250px]"></div>
</div>
)
})
useZoomImageMove
Zooms the image as the mouse moves over it.
API
function useZoomImageMove(): {
createZoomImage: QRL<(...args: Parameters<typeof createZoomImageMove>) => void>
zoomImageState: ZoomImageMoveState
}
State
interface ZoomImageMoveState {
zoomedImgStatus: "idle" | "loading" | "loaded" | "error"
}
Example
import { component$, useSignal, useVisibleTask$ } from "@builder.io/qwik"
import { useZoomImageMove } from "@zoom-image/qwik"
export default component$(() => {
const imageContainerRef = useSignal<HTMLDivElement>()
const { createZoomImage, zoomImageState } = useZoomImageMove()
useVisibleTask$(({ track }) => {
track(() => imageContainerRef.value)
if (imageContainerRef.value) {
createZoomImage(imageContainerRef.value, {
zoomImageSource: "/image-large.jpg"
})
}
})
return (
<div ref={imageContainerRef} class="relative h-[300px] w-[200px] overflow-hidden">
<img src="/image.jpg" alt="Move mouse to zoom" class="h-full w-full" />
</div>
)
})
useZoomImageClick
Toggles zoom when clicking on the image.
API
function useZoomImageClick(): {
createZoomImage: QRL<(...args: Parameters<typeof createZoomImageClick>) => void>
zoomImageState: ZoomImageClickState
}
State
interface ZoomImageClickState {
zoomedImgStatus: "idle" | "loading" | "loaded" | "error"
}
Example
import { component$, useSignal, useVisibleTask$ } from "@builder.io/qwik"
import { useZoomImageClick } from "@zoom-image/qwik"
export default component$(() => {
const imageContainerRef = useSignal<HTMLDivElement>()
const { createZoomImage, zoomImageState } = useZoomImageClick()
useVisibleTask$(({ track }) => {
track(() => imageContainerRef.value)
if (imageContainerRef.value) {
createZoomImage(imageContainerRef.value, {
zoomImageSource: "/image-large.jpg"
})
}
})
return (
<div ref={imageContainerRef} class="relative h-[300px] w-[200px] overflow-hidden cursor-pointer">
<img src="/image.jpg" alt="Click to zoom" class="h-full w-full" />
</div>
)
})
Cleanup
All hooks automatically handle cleanup when the component unmounts using Qwik’s cleanup function in useVisibleTask$. The cleanup removes event listeners and frees resources.
useVisibleTask$(({ cleanup }) => {
cleanup(() => {
// Resources are freed automatically
})
})
Qwik-Specific Patterns
Using $ for Event Handlers
Qwik requires the $ suffix for event handlers to enable lazy loading:
<button onClick$={() => {
setZoomImageState({ currentZoom: 2 })
}}>Zoom</button>
Tracking Reactive Dependencies
Use track() in useVisibleTask$ to re-run effects when dependencies change:
useVisibleTask$(({ track }) => {
track(() => zoomType.value)
// Re-runs when zoomType changes
if (zoomType.value === "wheel") {
createZoomImageWheel(containerRef.value)
}
})
Non-Serializable Values
The zoom instance is non-serializable and uses noSerialize() internally. The state itself is serializable and resumable.
Combining Multiple Zoom Types
import { component$, useSignal, useComputed$, useVisibleTask$ } from "@builder.io/qwik"
import {
useZoomImageWheel,
useZoomImageHover,
useZoomImageMove,
useZoomImageClick
} from "@zoom-image/qwik"
type Tab = {
name: string
current: boolean
value: "wheel" | "hover" | "move" | "click"
}
export default component$(() => {
const tabs = useSignal<Tab[]>([
{ name: "Wheel", current: true, value: "wheel" },
{ name: "Hover", current: false, value: "hover" },
{ name: "Move", current: false, value: "move" },
{ name: "Click", current: false, value: "click" }
])
const imageWheelContainerRef = useSignal<HTMLDivElement>()
const imageHoverContainerRef = useSignal<HTMLDivElement>()
const imageMoveContainerRef = useSignal<HTMLDivElement>()
const imageClickContainerRef = useSignal<HTMLDivElement>()
const zoomTargetRef = useSignal<HTMLDivElement>()
const { createZoomImage: createZoomImageWheel } = useZoomImageWheel()
const { createZoomImage: createZoomImageHover } = useZoomImageHover()
const { createZoomImage: createZoomImageMove } = useZoomImageMove()
const { createZoomImage: createZoomImageClick } = useZoomImageClick()
const zoomType = useComputed$(() => {
return tabs.value.find(tab => tab.current)?.value || "wheel"
})
useVisibleTask$(({ track }) => {
track(() => zoomType.value)
if (zoomType.value === "wheel" && imageWheelContainerRef.value) {
createZoomImageWheel(imageWheelContainerRef.value)
}
if (zoomType.value === "hover" && imageHoverContainerRef.value && zoomTargetRef.value) {
createZoomImageHover(imageHoverContainerRef.value, {
zoomImageSource: "/image-large.jpg",
customZoom: { width: 300, height: 500 },
zoomTarget: zoomTargetRef.value,
scale: 2
})
}
if (zoomType.value === "move" && imageMoveContainerRef.value) {
createZoomImageMove(imageMoveContainerRef.value, {
zoomImageSource: "/image-large.jpg"
})
}
if (zoomType.value === "click" && imageClickContainerRef.value) {
createZoomImageClick(imageClickContainerRef.value, {
zoomImageSource: "/image-large.jpg"
})
}
})
return (
<div>
<nav class="flex gap-4 mb-4">
{tabs.value.map((tab) => (
<button
key={tab.name}
onClick$={() => {
tabs.value = tabs.value.map(t => ({
...t,
current: t.name === tab.name
}))
}}
class={tab.current ? "active" : ""}
>
{tab.name}
</button>
))}
</nav>
{zoomType.value === "wheel" && (
<div ref={imageWheelContainerRef} class="h-[300px] w-[200px]">
<img src="/image.jpg" alt="Wheel zoom" class="h-full w-full" />
</div>
)}
{zoomType.value === "hover" && (
<div class="flex gap-4">
<div ref={imageHoverContainerRef} class="h-[300px] w-[200px]">
<img src="/image.jpg" alt="Hover zoom" class="h-full w-full" />
</div>
<div ref={zoomTargetRef}></div>
</div>
)}
{zoomType.value === "move" && (
<div ref={imageMoveContainerRef} class="h-[300px] w-[200px] overflow-hidden">
<img src="/image.jpg" alt="Move zoom" class="h-full w-full" />
</div>
)}
{zoomType.value === "click" && (
<div ref={imageClickContainerRef} class="h-[300px] w-[200px] overflow-hidden">
<img src="/image.jpg" alt="Click zoom" class="h-full w-full" />
</div>
)}
</div>
)
})
TypeScript Support
The Qwik adapter is written in TypeScript and provides full type definitions. Import types from @zoom-image/core:
import type {
ZoomImageWheelState,
ZoomImageWheelStateUpdate,
ZoomImageHoverState,
ZoomImageHoverStateUpdate,
ZoomImageMoveState,
ZoomImageClickState
} from "@zoom-image/core"