pinch

Pinch gesture recognition for two-finger zoom interactions. Tracks distance, ratio, velocity, and center point between two pointers.

Terminal window
npm install --save @cereb/pinch

Basic Usage

import { pinch } from "@cereb/pinch";
pinch(element).on((signal) => {
const { phase, ratio, centerX, centerY } = signal.value;
if (phase === "change") {
element.style.transform = `scale(${ratio})`;
}
});

Signature

function pinch(target: EventTarget, options?: PinchOptions): Stream<PinchSignal>

Options

OptionTypeDefaultDescription
thresholdnumber0Minimum distance change (px) before gesture starts

Signal Value

The signal.value contains:

PropertyTypeDescription
phase"start" | "change" | "end" | "cancel"Current gesture phase
initialDistancenumberDistance between pointers at start (px)
distancenumberCurrent distance between pointers (px)
rationumberCurrent distance / initial distance
deltaDistancenumberDistance change since last event (px)
velocitynumberDistance change velocity (px/ms)
centerXnumberCenter X between pointers (clientX)
centerYnumberCenter Y between pointers (clientY)
pageCenterXnumberCenter X between pointers (pageX)
pageCenterYnumberCenter Y between pointers (pageY)

Phase Lifecycle

two pointers down → (threshold met) → "start" → "change"* → "end" or "cancel"
  • start: Emitted once when threshold is met
  • change: Emitted repeatedly during pinch
  • end: One pointer released normally
  • cancel: Gesture interrupted (e.g., system event)

With Zoom Operator

Combine with the zoom operator for bounded scale calculations:

import { pinch } from "@cereb/pinch";
import { zoom } from "cereb/operators";
pinch(element)
.pipe(zoom({ minScale: 0.5, maxScale: 3.0 }))
.on((signal) => {
const { scale, centerX, centerY } = signal.value;
element.style.transform = `scale(${scale})`;
});

Advanced: pinchRecognizer

Use as an operator with custom pointer sources:

import { multiPointer } from "cereb";
import { multiPointerSession } from "cereb/operators";
import { pinchRecognizer } from "@cereb/pinch";
multiPointer(element, { maxPointers: 2 })
.pipe(
multiPointerSession(2),
pinchRecognizer({ threshold: 10 })
)
.on((signal) => { /* ... */ });