3

I have several svg use elements within an svg element, with event handlers for touchstart/move/end attached to the svg element. When a user touches the svg use element on the screen and moves their finger around, the element moves around the screen as desired.

//Exact code in fiddle is different, this is example
<svg id="parent" viewBox="0 0 1000 1000">
   <use href="test.svg" width="100" height="100" x="100" y="100">
   <use href="test2.svg" width="100" height="100" x="100" y="100">
</svg>
//Exact code in fiddle is different, this is example
let parentSVG = document.getElementById("parent");
parentSVG.addEventListener('touchstart', Handle_DragStart); //mousedown too
parentSVG.addEventListener('touchmove', Handle_Drag); //mousemove too
parentSVG.addEventListener('touchend', Handle_DragEnd); //mouseup too

The problem is that I want the dragged element to always be drawn on top of the others, and the elements are drawn in DOM-order (first element in DOM first). To handle this, in my touchstart event handler I'm moving the element the user touched to the end of the list via appendChild():

//Exact code in fiddle is different, this is example
let mid_move = false;
function Handle_DragStart (event)
{
   //Needed to ignore other touch events while dragging
   if(mid_move)
      return;
   mid_move = true;

   //The event.target is the svg use element. 
   //It is already attached to the parentSVG. 
   //This just moves it to the end of the list of children. 
   parentSVG.appendChild(event.target);
}

This works like a charm for mousedown and mousemove but not touchstart and touchmove. After doing this the touchmove event doesn't fire until I touch the screen a second time, presumably because the appendChild() call in the touchstart handler doesn't change anything at that point?

Am I invalidating the event? How should I handle this situation?

EDIT:

JSFiddle example. //Set append_child to true/false to see behavior

P. Mattione
  • 131
  • 4
  • 1
    Please provide a [minimum reproducible example](https://stackoverflow.com/help/minimal-reproducible-example), i.e. providing simple sample `test.svg` and `test2.svg` – Jan Stránský Sep 02 '20 at 08:04
  • 1
    @JanStránský added a working example if you're interested. – P. Mattione Sep 03 '20 at 05:19
  • 2
    Interesting. I have played with your example, touchevents does not work for `use` after `appendChild` them. I have try to postpone the `appendChild` with `setTimout`, workekd nicely until the DOM change. BUT, it works for "plain" `rect`. Is e.g. using invisible covers and adjusting position of the visible elements according to the dragged cover an option for you? – Jan Stránský Sep 03 '20 at 13:07
  • Well, I have 32 draggable elements on the screen. I don't know what kind of performance hit that will be but I can certainly try it. I'm new to javascript though and wonder what's happening to the events. You'd think since the handler is attached to the background it wouldn't be effected ... – P. Mattione Sep 03 '20 at 21:26
  • From the description I would not be afraid about performance – Jan Stránský Sep 03 '20 at 21:30
  • This sounds similar to https://stackoverflow.com/questions/33298828/touch-move-event-dont-fire-after-touch-start-target-is-removed, but it's not the same problem. This one seems to be *specific to tags*, because it works with other SVG tags like rect and path. – Daan Michiels Sep 19 '22 at 17:44

1 Answers1

0

I circumvented the problem with a modified form of @JanStránský suggestion. I placed a transparent svg rectangle covering the entire drag area to act as a drag surface, and listed this at the end of the list of svg elements.

Then when handling touchstart, I used the mouse position to determine which svg element underneath this clear svg rectangle the user was selecting. I then called parentSVG.insertBefore(selected, clear_rect) so that the element was drawn above everything else but beneath this drag surface (so subsequent drags would work).

The draggable elements are all laid out in (and snapped-to) a grid, so mouse position -> selected element is easily determined. Otherwise you may need one clear rect per draggable element as suggested.

This solution seems to prevent me from using CSS features cursor: grab; when over the element since the large drag surface is in the way. Maybe I'll just implement it manually.

P. Mattione
  • 131
  • 4