Search code examples
javascriptgoogle-chromeeventstouchpointer-events

Touch Move event don't fire after Touch Start target is removed


I'm trying to implement a drag-like functionality using the next pattern:

  • Subscribe to marker Pointer Down event.
  • When Down event fires subscribe to Window Pointer Move and Up events and remove marker.
  • Perform some actions while Move.
  • When Up event fires unsubscribe from Move and Up.

This works for Mouse events, but doesn't work for Touch events. They don't fire after Touch Start target element is removed. I tried to use Pointer Events Polyfill but it doesn't work either.

I'm using Chrome Dev Tools to emulate touch events. See the sample:

initTestBlock('mouse', {
  start: 'mousedown',
  move: 'mousemove',
  end: 'mouseup'
});
initTestBlock('touch', {
  start: 'touchstart',
  move: 'touchmove',
  end: 'touchend'
});
initTestBlock('touch-no-remove', {
  start: 'touchstart',
  move: 'touchmove',
  end: 'touchend'
}, true);

function initTestBlock(id, events, noRemove) {
  var block = document.getElementById(id);
  var parent = block.querySelector('.parent');
  var target = block.querySelector('.target');
  target.addEventListener(events.start, function(e) {
    console.log(e.type);
    if (!noRemove) {
      setTimeout(function() {
        // Remove target
        target.parentElement.removeChild(target);
      }, 1000);
    }

    function onMove(e) {
      console.log(e.type);
      var pt = getCoords(e);
      parent.style.left = pt.x + 'px';
      parent.style.top = pt.y + 'px';
    }

    function onEnd(e) {
      console.log(e.type);
      window.removeEventListener(events.move, onMove);
      window.removeEventListener(events.end, onEnd);
    }

    window.addEventListener(events.move, onMove);
    window.addEventListener(events.end, onEnd);

  });
}

// Returns pointer coordinates
function getCoords(e) {
  if (e instanceof TouchEvent) {
    return {
      x: e.touches[0].pageX,
      y: e.touches[0].pageY
    };
  }
  return {
    x: e.pageX,
    y: e.pageY
  };
}

window.addEventListener('selectstart', function() {
  return false;
}, true);
.parent {
  background: darkred;
  color: white;
  width: 10em;
  height: 10em;
  position: absolute;
}
.target {
  background: orange;
  width: 4em;
  height: 4em;
}
#mouse .parent {
  left: 0em;
}
#touch .parent {
  left: 11em;
}
#touch-no-remove .parent {
  left: 22em;
}
<div id="mouse">
  <div class="parent">Mouse events
    <div class="target">Drag here</div>
  </div>
</div>
<div id="touch">
  <div class="parent">Touch events
    <div class="target">Drag here</div>
  </div>
</div>
<div id="touch-no-remove">
  <div class="parent">Touch (no remove)
    <div class="target">Drag here</div>
  </div>
</div>


Solution

  • The trick is to hide element until touch move finishes, but not to remove it. Here is some example (enable Touch Mode in Chrome Dev Tools and select some device or use real device): https://jsfiddle.net/alexanderby/na3rumjg/

    var marker = document.querySelector('circle');
    var onStart = function(startEvt) {
      startEvt.preventDefault(); // Prevent scroll
      marker.style.visibility = 'hidden'; // Hide target element
      var rect = document.querySelector('rect');
      var initial = {
        x: +rect.getAttribute('x'),
        y: +rect.getAttribute('y')
      };
      var onMove = function(moveEvt) {
        rect.setAttribute('x', initial.x + moveEvt.touches[0].clientX - startEvt.touches[0].clientX);
        rect.setAttribute('y', initial.y + moveEvt.touches[0].clientY - startEvt.touches[0].clientY);
      };
      var onEnd = function(endEvt) {
        window.removeEventListener('touchmove', onMove);
        window.removeEventListener('touchend', onEnd);
        marker.removeEventListener('touchstart', onStart);
        marker.parentElement.removeChild(marker); // Remove target element
      };
      window.addEventListener('touchmove', onMove);
      window.addEventListener('touchend', onEnd);
    };
    marker.addEventListener('touchstart', onStart);
    <svg>
      <circle r="20" cx="50" cy="20" cursor="move"/>
      <rect x="10" y="50" width="80" height="80" />
    </svg>