Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using W3C PointerEvent API and be able to detect a double click?

Writing a rich web application I have the need to support all types of users, some are using a mouse, some are using a touch screen (e.g. on a mobile device). So the W3C PointerEvent API is exactly what I need to handle the user interactions. This works fine, except for one important case: double clicks.

There are a few things in the way:

  • There is no (high level) event like "pointerdblclick", only the low level pointerdown and pointerup are available.
  • To fix that and calculate the double click myself it is often recommended to use the property detail of the emitted event and check whether it has the value 2. But this property is always 0 in my case.
  • But even tracking this number myself will not work perfectly, as the OS (and possible setting by the user) is defining the maximum delay that is making two clicks to a double click.

So what can I do to react on a double click by mouse or a double tab by touch when all other user interactions are handled by listening to the PointerEvents? Can this be even made in such a way that the OS setting of the double click delay is respected?

Note 1: I still must be able to detect a normal click (by listening to pointerdown and pointerup) as well as a drag (by listening to pointerdown, pointermove and later pointerup)
Note 2: I'm using "pure" JavaScript and modern browsers, jQuery is no option.

like image 485
Chris Avatar asked Dec 06 '25 18:12

Chris


1 Answers

Here's a runnable example of listening to both the low level pointer events for dragging and the high level click events. We'll get a click event when pressing the mouse button, moving the mouse, and releasing the button. But we probably want to treat that as a drag. So I have a variable gotMoveEvent to detect that there was a drag.

// code based on https://www.redblobgames.com/making-of/draggable/

const messages = document.querySelector("figcaption");
const draggable = document.querySelector("circle");

let dragging = false;
let gotMoveEvent = false;

draggable.onpointerdown = (event) => {
   if (event.button !== 0) return; // only drag on left click
   draggable.classList.add("dragging"); // css feedback
   event.currentTarget.setPointerCapture(event.pointerId); // capture
   dragging = true;
   gotMoveEvent = false;
   messages.innerText = "Click or drag?";
}

draggable.onpointerup = (event) => {
   dragging = false;
   draggable.classList.remove("dragging"); // css feedback
}

draggable.onpointercancel = draggable.onpointerup;

draggable.onpointermove = (event) => {
   if (!dragging) return;
   gotMoveEvent = true;
   messages.innerText = "Dragging";
   let p = convertPixelToSvgCoord(event);
   draggable.setAttribute("transform", `translate(${p.x},${p.y})`);
}

draggable.onclick = (event) => {
   if (gotMoveEvent) {
      messages.innerText = "Ignoring click event because we were dragging";
      event.preventDefault();
      return;
   }
   messages.innerText = "Got click";
}

draggable.ondblclick = (event) => {
   // maybe: check for gotMoveEvent here too?
   messages.innerText = "Got dblclick";
}



/** Convert from event coordinate space (on the page) to SVG coordinate
 * space (within the svg, honoring responsive resizing, width/height,
 * and viewBox) */
function convertPixelToSvgCoord(event, relativeTo=event.currentTarget.ownerSVGElement) {
    // if relativeTo is the <svg> then its ownerSVGElement is null, so we want to point back to the <svg>
    // but otherwise we assume it's a child of <svg> and we want to find the <svg>
    let p = (relativeTo.ownerSVGElement ?? relativeTo).createSVGPoint();
    p.x = event.clientX;
    p.y = event.clientY;
    return p.matrixTransform(relativeTo.getScreenCTM().inverse());
}
.draggable { fill: hsl(0 50% 50%); cursor: grab; }
.draggable.dragging { fill: hsl(200 50% 50%); cursor: grabbing; user-select: none; }
<figure>
<figcaption>Drag or click</figcaption>
<svg viewBox="-200 -50 400 100" style="background:hsl(60 5% 95%)">
  <circle class="draggable" r="20" />
</svg>
</figure>
like image 161
amitp Avatar answered Dec 08 '25 06:12

amitp



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!