2

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.

Chris
  • 3,265
  • 5
  • 37
  • 50
  • Does this help? https://developer.mozilla.org/en-US/docs/Web/API/Element/dblclick_event – evolutionxbox Apr 21 '23 at 14:19
  • Sadly: no it doesn't. As my listeners to the `pointerdown` and `pointerup` are preventing it from being emitted. – Chris Apr 21 '23 at 14:22
  • I believe `click` and `dblclick` are the high level events, and mousedown/mouseup & pointerdown/pointerup are the low level events. The browser should be sending you both the low and high level events, and you can react to `dblclick`. My guess is that the tricky thing is figuring out whether to cancel (preventDefault) some of the events. – amitp May 13 '23 at 15:48

1 Answers1

0

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>
amitp
  • 1,195
  • 1
  • 10
  • 10