Skip to content

Granular Events

Beyond the main onViewStateChange callback, useZoomPinch provides six granular event callbacks for gesture lifecycle tracking.

CallbackFires whenTrigger
onPanStartPointer drag beginspointerdown
onPanEndPointer drag ends (all pointers up)pointerup
onZoomStartFirst ctrl+wheel eventwheel with ctrlKey
onZoomEnd150ms after last ctrl+wheel eventDebounced
onPinchStartTwo-finger touch beginstouchstart (2+ touches)
onPinchEndFingers reduced below 2touchend

All callbacks receive the current ViewState as their argument:

useZoomPinch({
containerRef,
onPanStart: (view) => console.log("Pan started at", view),
onPanEnd: (view) => console.log("Pan ended at", view),
onZoomStart: (view) => console.log("Zoom started at", view),
onZoomEnd: (view) => console.log("Zoom ended at", view),
onPinchStart: (view) => console.log("Pinch started at", view),
onPinchEnd: (view) => console.log("Pinch ended at", view),
})

Change the cursor during drag:

const [isDragging, setIsDragging] = useState(false)
useZoomPinch({
containerRef,
onPanStart: () => setIsDragging(true),
onPanEnd: () => setIsDragging(false),
})
// In JSX
<div ref={containerRef} style={{ cursor: isDragging ? "grabbing" : "grab" }}>

Track gesture frequency:

useZoomPinch({
containerRef,
onZoomStart: () => analytics.track("canvas_zoom"),
onPinchStart: () => analytics.track("canvas_pinch"),
})

Save view state only when gestures finish:

useZoomPinch({
containerRef,
viewState,
onViewStateChange: setViewState,
onPanEnd: (view) => saveToBackend(view),
onZoomEnd: (view) => saveToBackend(view),
onPinchEnd: (view) => saveToBackend(view),
})

Hide toolbars during interaction:

const [interacting, setInteracting] = useState(false)
useZoomPinch({
containerRef,
onPanStart: () => setInteracting(true),
onPanEnd: () => setInteracting(false),
onPinchStart: () => setInteracting(true),
onPinchEnd: () => setInteracting(false),
})
// Fade out toolbar during gestures
<div style={{ opacity: interacting ? 0.3 : 1, transition: "opacity 150ms" }}>
<Toolbar />
</div>

onZoomEnd is debounced by 150ms because trackpad zoom generates many rapid wheel events. The callback fires once, 150ms after the last zoom wheel event:

wheel (ctrlKey) → onZoomStart
wheel (ctrlKey) → (reset timer)
wheel (ctrlKey) → (reset timer)
... 150ms silence ...
→ onZoomEnd