Skip to article

pinch

pinch() lets users zoom and rotate elements with two fingers or a trackpad, around the point between their fingers.

Motion's pinch function lets users zoom and rotate elements with two fingers.

pinch("img")

It works with multiple input types:

  • Two pointers.

  • Trackpads.

  • wheel events with ctrlKey.

  • Safari's gesturestart, gesturechange and gestureend events.

The element will zoom and rotate around the point between the fingers (or under the cursor for wheel/trackpad events).

Pinch to zoomOpen example

Usage

Import

pinch can be imported via "motion".

import { pinch } from "motion"

Make an element pinchable

pinch accepts an Element, an array of elements, or a CSS selector.

pinch(document.getElementById("photo"))

pinch(".gallery img")

pinch sets touch-action: none on the element, so the browser doesn't scroll or zoom the page while a user touches it.

The second touch can land beside the element. This makes small elements, like thumbnails, easier to pinch.

Options

Options go in the second argument:

pinch(".photo", { scale: { min: 1, max: 4 } })

See the full range of options.

Pinch start and end

pinch accepts a function that's called when a pinch starts. It receives the element and the event that started the pinch and can return an event that will trigger when the pinch ends:

pinch(".photo", (element, startEvent) => {
  console.log("pinch start")

  return (endEvent, info) => {
    console.log("pinch end at", info.scale)
  }
})

Track a pinch

To follow the pinch as it changes, return an object of handlers instead. onPinch is called each frame the pinch changes, and onComplete when it ends.

pinch(".photo", () => ({
  onPinch: (event, info) => console.log(info.scale),
  onComplete: (event, info) => console.log("done"),
}))

info describes the gesture since it started:

  • point: The point between the fingers (or the cursor), in client coordinates.

  • offset: How far point has moved.

  • scale: How much the fingers have spread. 2 means twice as far apart.

  • rotate: How far the fingers have turned, in degrees.

Scale and rotate

By default, pinch scales, rotates and moves the element. To turn any of these off, set scale, rotate or pan to false.

// Zoom without rotating
pinch(".photo", { rotate: false })

// Rotate only, around the element's centre
pinch(".dial", { scale: false, pan: false })

Limits

scale and rotate accept min and max limits. Rotation limits are in degrees.

pinch(".photo", {
  scale: { min: 1, max: 4 },
  rotate: { min: -45, max: 45 }
})

Elastic

elastic sets how far the element can be pinched past its min/max limits. It works like drag's elastic: 0 stops at the limit and 1 doesn't resist. It defaults to 0.35.

pinch(".photo", {
  scale: { min: 1, max: 4 },
  elastic: 0.2
})

To set constraints for each side, pass min and max.

// Stop hard at 1x, stretch past 4x
pinch(".photo", {
  scale: { min: 1, max: 4 },
  elastic: { min: 0, max: 0.5 }
})

Snap rotation

rotate.snap rotates the element to the nearest multiple of snap degrees when the user lets go.

pinch(".photo", { rotate: { snap: 90 } })
Pinch to rotateOpen example

Snap to origin

To animate the element back to where it started when the user lets go, set snapToOrigin to true. scale returns to 1, x and y to 0, and rotate turns the shortest way to upright.

pinch(".photo", { snapToOrigin: true })

Transition

transition changes the spring that animates the element back inside its limits, to a snap angle or to its origin. It accepts spring options like stiffness, damping, bounce and visualDuration.

pinch(".photo", {
  scale: { max: 4 },
  transition: { visualDuration: 0.3, bounce: 0.2 }
})

Animate after a pinch

pinch, animate and drag use the same scale, rotate, x and y values for an element. So animate can move the element after a pinch, for anything the snap options don't cover:

pinch(".photo", (element) => (endEvent, info) => {
  animate(element, { scale: 1, rotate: 0, x: 0, y: 0 }, { type: "spring" })
})

If a pinch starts while animate changes these values, the pinch stops those animations.

With drag

pinch works with drag on the same element:

drag(".photo")
pinch(".photo")

While a user pinches, the points under their fingers stay put. When one finger lifts, the drag carries on from there, without a jump. And a zoom doesn't end in a fling, because drag's momentum only counts movement after the pinch.

With press

A pinch is never a press. If an element has both press and pinch, a pinch ends the press as a cancel, so info.success is false.

On an element without drag, a pinch also stops a draggable parent from dragging.

Stop the gesture

pinch returns a function. Call it to remove the gesture and restore the element's original touch-action.

const stopPinch = pinch(".photo")

stopPinch()

If a pinch is running, it ends, and its end callback is called.

Options

scale

Default: true

Scale the element with the gesture. Pass { min, max } to limit the scale.

rotate

Default: true

Rotate the element with the fingers. Pass { min, max } to limit the rotation, in degrees, and snap to rotate to the nearest multiple of snap degrees on release.

pan

Default: true

Move the element with the point between the fingers. With pan set to false, the element scales and rotates around its centre.

origin

Default: "pointer", or the centre when pan is false

The point that scale and rotate pivot around. "pointer" is the point between the fingers, or the cursor on a trackpad.

Pass { x, y } to pivot around a point on the element, as a fraction of its width and height. { x: 0.5, y: 0.5 } is the centre, so the element stays centred while it zooms. pan still moves it with the fingers, unless pan is false.

pinch(".photo", { origin: { x: 0.5, y: 0.5 }, pan: false })

elastic

Default: 0.35

How far scale and rotate can go past their limits. 0 stops at the limit, 1 doesn't resist. Pass { min, max } to set each side.

snapToOrigin

Default: false

On release, animate scale back to 1 and x and y back to 0, and turn rotate the shortest way to upright (the nearest multiple of 360).

transition

The spring that animates the element back inside its limits, to a snap angle or to its origin.