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:
pointerdblclick", only the low level pointerdown and pointerup are available.detail of the emitted event and check whether it has the value 2. But this property is always 0 in my case.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.
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With