Draggable objects, different types

 from Red Blob Games
4 Jan 2023

Things to try with a mouse:

Things to try with touch:


 1  SVG single element#

Draggable circle

I use this helper function quite a bit when working with SVG:

/** 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) {
    const svg = event.currentTarget.ownerSVGElement;
    let p = svg.createSVGPoint();
    p.x = event.clientX;
    p.y = event.clientY;
    return p.matrixTransform(svg.getScreenCTM().inverse());

This function gives coordinates for the <svg> doesn’t handle other SVG transforms; see the parent transform section for a version that does.

 2  SVG nested element with text#

Draggable circle + text

 3  Canvas painting#

The same drag event handlers can be used for painting instead of moving an object:

Drag to paint on the canvas

 4  Canvas dragging#

Drag the circle to move it

 5  HTML div absolute positioned#

Drag me
Drag the box to move it

 6  HTML div css transform#

Drag me
Drag the box to move it
abc LINK xyz
Try left, middle, right clicking the link

However in this demo you can click the link or drag with it:

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.

 8  Draggable with a nested draggable#

I don’t have a test here but I wrote one on another site[3], where both the yellow and red boxes are draggable. To handle this case:

 9  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

 10  Coordinate transform on parent#

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

The problem is that the convertPixelToSvgCoord 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 convertPixelToSvgCoord 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 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());

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.

 11  Scrubbable number#

On some of my pages, like Probability of Damage Rolls, I have some numbers in the text of the page, and those numbers are “scrubbable”. The reader can drag them left/right to change the value. I want to be able to handle that with the same dragging code that I use for dragging objects.

Scrubbable number can be dragged left/right to set the value

It’s a constrained drag. I ignore the y value from the drag. But the object doesn’t actually move around on the screen. The x value is displayed as text instead of being used to set the position. This example, and those on my constrained dragging page, show how the same basic recipe can be used for a wide variety of situations.

 12  Painting state#

I don’t have a demo here but look at Painting on a map, where the initialpointerdown event remembers what color we’re painting, and then all subsequent pointermove events paint in that color. If you first painted in blue, then you continue painting in blue until the pointerup.