Your Location is: Home > Javascript

Javascript touchmove event not working after event.target moved in DOM in touchstart

From: Sweden View: 2113 P. Mattione 

Question

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

Best answer

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.