Skip to article

press

press() detects press gestures, including double press, long press and press to repeat, with keyboard support and pointer filtering built in.

Motion's press function detects press gestures, firing events when they start, end or cancel.

It's different to native browser events like "pointerstart" etc in that press automatically filters out secondary pointer events, like right clicks or a second touch point.

It also expands on "click" by being great for accessibility. Every element with a press gesture automatically becomes keyboard accessible via focus and the enter key.

press("button", (element) => {
  console.log("press started on", element)

  return () => console.log("press ended")
})

press is also:

  • Clean: Automatically manages event listeners

  • Convenient: Accepts either elements or CSS selectors for attaching multiple listeners at once

  • Lazy: Attaches only the listeners needed

It can be used to fire any function, but also to start and stop animations:

press("button", (element) => {
  animate(element, { scale: 0.9 })

  return () => animate(element, { scale: 1 })
})
PressOpen example

Video overview

Video guideOpen video

Usage

Import

press can be imported via "motion".

import { press } from "motion"

Press start

press can detect press start gestures on either a Element/array of elements:

press(
  document.getElementById("my-id"),
  () => {
    console.log("my-id pressed!")
  }
)

It also accepts CSS selectors to detect press start on multiple elements:

press("a", () => console.log("link pressed"))

The callback is provided the element being pressed, and the triggering PointerEvent:

press("div:nth-child(2)", (element, startEvent) => {
  console.log("Pressed", element)
  console.log("At", startEvent.clientX, startEvent.clientY)
})

Press end

The press start function can optionally return a function that will be called when the press gesture ends:

press(element, (element, startEvent) => {
  console.log("press start")
  
  return (endEvent) => {
    console.log("press end")
  }
})

The press end callback is passed a second argument, info. It currently has one property, success. If true, the press was successfully completed, like a click. If false, the press ended outside the element.

press(element, () => {
  return (endEvent, info) => {
    console.log("press ", info.success ? "end" : "cancel")
  }
})

When using keyboard accessibility, success will be false if the element is blurred while the enter key is held.

Press handlers

Instead of a function, the press start callback can return an object of handlers for this press.

press(element, () => {
  return {
    onActive: () => console.log("active"),
    onHold: (progress) => console.log("held", progress),
    onRepeat: (count) => console.log("repeat", count),
    onComplete: (endEvent, info) => console.log("end", info.success)
  }
})
  • onComplete: Called when the press ends. This is the same as returning a function.

  • onActive: Called once, when the press becomes active while it is still held. Without hold, this is on press down. With hold, it's when the hold time is reached.

  • onHold: Called every frame while the press is held, with progress from 0 to 1. It counts up over hold, then over each repeat. It's only called when hold or repeat is set.

  • onRepeat: Called every repeat seconds while the press is held, with the number of repeats so far.

Double press

Set count to the number of quick presses the gesture needs. For example, 2 for a double press, or 3 for a triple press.

press(element, () => {
  return (endEvent, info) => {
    if (info.success) console.log("double press!")
  }
}, { count: 2 })

The press start callback fires on the first press, so a press style can show at once. The gesture only succeeds after the last press. If the next press comes too late or too far away, the gesture is cancelled and success is false.

countTimeout sets the time allowed between presses, in seconds.

Double tap to likeOpen example

Long press

Set hold to the number of seconds the press must be held for.

press(element, () => {
  return {
    onHold: (progress) => {
      ring.style.strokeDashoffset = 1 - progress
    },
    onActive: () => console.log("long press!"),
    onComplete: (endEvent, info) => {
      if (!info.success) console.log("released too early")
    }
  }
}, { hold: 1 })

onHold makes it easy to show progress as the press is held. onActive fires the moment the hold time is reached, while the pointer is still down.

If the press is released before hold, or the pointer moves more than a few pixels, the gesture is cancelled. This means that scrolling on a touch device doesn't trigger a long press. press also stops the context menu that touch devices open on a long press.

Long pressOpen example

Press and hold to repeat

Set repeat to fire onRepeat every repeat seconds while the press is held. This is useful for buttons like steppers and media controls.

press(plusButton, () => {
  increment()

  return {
    onActive: increment,
    onRepeat: increment
  }
}, { hold: 0.4, repeat: 0.08 })

With hold, the repeats start once the hold time is reached. Here, a quick press adds one, and holding the button keeps counting.

Press and hold to repeatOpen example

Cancelling gesture

press returns a function that, when fired, will cancel all active event handlers associated with the gesture.

const cancelPress = press(element, callback)

cancelPress()

Options

passive

Default: true

If set to false, it'll be possible to call event.preventDefault() but the gesture will be less performant. Learn more about passive events.

once

Default: false

If set to true, each provided element will fire their gesture only once.

count

Default: 1

The number of quick, consecutive presses needed for the gesture to succeed. For example, 2 for a double press.

press(element, onPress, { count: 2 })

countTimeout

Default: 0.3

The maximum time, in seconds, between the end of one press and the start of the next for both to count towards count.

hold

Default: 0

The time, in seconds, the last press must be held for the gesture to succeed. Releasing earlier cancels the gesture. See long press.

press(element, onPress, { hold: 0.5 })

repeat

While the last press is held, and once hold is reached, call onRepeat every repeat seconds. See press and hold to repeat.

press(element, onPress, { repeat: 0.1 })