Draggable objects, different types

 from Red Blob Games
4 Jan 2023

Things to try with a mouse:

Things to try with touch:


The Webplatform Tests folk have run a lot more tests across browsers[3], like what happens if you remove an element in the middle of a pointer event.

abc LINK xyz
Try left, middle, right clicking the link

I tweaked the demo so you can click the link in Chrome:

abc LINK xyz
Now try clicking the link

This was a little tricky and I don’t know if it handles all cases but:

In general though I don’t try to do this type of thing. I don’t put links inside my draggable objects.

 2  Click events#

This stackoverflow question[4] made me wonder what happens when we have both click and drag event handlers on the same object. The idea here is to listen to both types of events. If we detect a pointermove, then don’t process the click. Maybe it needs to have some threshold of how many pixels we moved before we suppress the click. I also wrote up a full demo on the stackoverflow page.

Click ordrag me
Draggable + clickable

 3  Coordinate transform on parent#

The draggable element might be inside another element that has a transform applied to it.

The problem is that the eventToSvgCoordinates function I use calculates coordinates relative to the <svg> element. This is fine if your draggable object is positioned relative to the <svg>’s coordinate system, but in this test there’s <svg> then <g> with a rotate() on it. The pointer coordinates are in screen coordinates, and we need to unrotate them if we want the draggable to be placed within the rotated <g>.

Draggable inside a transformed SVG element

To handle this, extend the eventToSvgCoordinates function to take an element the coordinate should be relative to, instead of assuming the coordinates should be in the <svg>’s coordinate space.

/** Convert from event coordinate space (on the page) to SVG coordinate
 * space (within the svg, honoring responsive resizing, width/height,
 * and viewBox) */
function eventToSvgCoordinates(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());

However, this function only handles transforms on the SVG elements. For transforms on non-SVG elements, I don’t know what to do. This is an unsolved issue, not only for me, but also other libraries like d3.js. See d3-selection issue #67[5] and firefox bug #1610093[6] . There’s probably something we can do using window.getComputedStyle(element) but I haven’t tried.