Skip to content

Rotation

useZoomPinch supports rotation via two-finger twist gestures and programmatic methods. Rotation is disabled by default — opt in via the gestures option.

const { view, rotateTo, rotateBy } = useZoomPinch({
containerRef,
gestures: { rotate: true },
})

Add rotate() to your existing transform:

<div
style={{
transform: `translate(${view.x}px, ${view.y}px) scale(${view.zoom}) rotate(${view.rotation ?? 0}deg)`,
transformOrigin: "0 0",
}}
>
{/* Your content */}
</div>

When gestures.rotate is true, two-finger touch gestures detect both distance change (zoom) and angle change (rotation) simultaneously. The angle between touch points is tracked from touchstart through touchmove.

You can enable rotation alongside zoom (default) or independently:

// Both zoom and rotate during pinch
useZoomPinch({ containerRef, gestures: { rotate: true } })
// Rotate only — no zoom during pinch
useZoomPinch({ containerRef, gestures: { zoom: false, rotate: true } })

Set rotation to an absolute angle in degrees:

rotateTo(0) // reset rotation instantly
rotateTo(90, { animate: true }) // rotate to 90° with animation
rotateTo(-45, { animate: true, duration: 500 })

Rotate by a relative delta. Positive values rotate clockwise:

rotateBy(90, { animate: true }) // rotate 90° clockwise
rotateBy(-45, { animate: true }) // rotate 45° counter-clockwise

Subscribe to rotation lifecycle:

useZoomPinch({
containerRef,
gestures: { rotate: true },
onRotateStart: (view) => console.log("Rotation started", view.rotation),
onRotateEnd: (view) => console.log("Rotation ended", view.rotation),
})

resetView() resets rotation to 0 along with position and zoom:

// Resets to { x: 0, y: 0, zoom: 1, rotation: 0 }
resetView({ animate: true })
function RotatableCanvas() {
const containerRef = useRef<HTMLDivElement>(null)
const { view, rotateBy, resetView } = useZoomPinch({
containerRef,
gestures: { rotate: true },
})
return (
<div>
<div style={{ display: "flex", gap: 8 }}>
<button onClick={() => rotateBy(-90, { animate: true })}>↺ 90°</button>
<button onClick={() => rotateBy(90, { animate: true })}>↻ 90°</button>
<button onClick={() => resetView({ animate: true })}>Reset</button>
<span>{Math.round(view.rotation ?? 0)}°</span>
</div>
<div ref={containerRef} style={{ overflow: "hidden", touchAction: "none" }}>
<div
style={{
transform: `translate(${view.x}px, ${view.y}px) scale(${view.zoom}) rotate(${view.rotation ?? 0}deg)`,
transformOrigin: "0 0",
}}
>
{/* Your content */}
</div>
</div>
</div>
)
}