触摸开始目标被移除后,触摸移动事件不触发。

13

我正在尝试使用以下模式实现类似拖拽的功能:

  • 订阅标记指针按下事件。
  • 当按下事件触发时,订阅 Window 指针移动和释放事件,并删除标记。
  • 在移动时执行一些操作。
  • 当释放事件触发时,取消订阅移动和释放事件。

这对于鼠标事件有效,但对于触摸事件无效。它们在触摸开始目标元素被移除后不再触发。我尝试使用Pointer Events Polyfill,但也不起作用。

我正在使用 Chrome Dev Tools 来模拟触摸事件。请参见示例:

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>


我有一个相关的问题。你在此期间找到了解决方案吗? - Sebastian vom Meer
@SebastianvomMeer 请看我的回答 https://dev59.com/LlwX5IYBdhLWcg3w2iku#34980275 - Alexander Shutau
2个回答

23

实际上,根据文档,

如果目标元素从文档中移除,事件仍将以其为目标,因此不一定会冒泡到窗口或文档。 如果存在在触摸元素时可能被删除的风险,则最佳做法是直接将触摸侦听器附加到目标。

事实证明,解决方案是将 touchmovetouchend 侦听器附加到 event.target 本身,例如:

element.addEventListener("touchstart", (event) => {
    const onTouchMove = () => {
        // handle touchmove here
    }
    const onTouchEnd = () => {
        event.target.removeEventListener("touchmove", onTouchMove);
        event.target.removeEventListener("touchend", onTouchEnd);
        // handle touchend here
    }
    event.target.addEventListener("touchmove", onTouchMove);
    event.target.addEventListener("touchend", onTouchEnd);
    // handle touchstart here
});

即使从DOM中移除了event.target元素,事件仍将正常触发并提供正确的坐标。

1
这个救了我的一天!:D - raeffs
4
为什么这个能够工作?event.target位于DOM元素内部,因此如果该元素从DOM中移除,目标也会被移除。如果目标也从DOM中移除了,那么如何使目标上的事件冒泡呢? - poshest
最近我在自己的脚本中遇到了一个问题,我假设这是一个 bug。我添加了一个带有 passive: false 选项的 touchmove 事件到 body 中以便于 preventDefault,然后将其删除。一旦我将其删除,它就会阻止具有 active 的 CSS 类。我花了几个小时来尝试修复它,最终解决方法是在删除第一个函数后立即添加一个新的、***"空白""空函数"***到 touchmove 事件中。非常奇怪。我真的很想知道为什么会发生这种情况。它是否出于同样的原因?! - oldboy
2
这个为什么没有标记为答案?这个救了我的一天!非常感谢。 - سعيد

5

技巧在于在触摸移动完成之前隐藏元素,而不是删除它。以下是一些示例(在Chrome Dev Tools中启用Touch Mode并选择某些设备或使用真实设备):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>


最近我在自己的脚本中遇到了一个问题,我认为这可能是一个错误。我给 body 添加了一个带有 passive: false 选项的 touchmove 事件来执行 preventDefault,然后将其删除。一旦我将其删除,它就会阻止具有 active 的 CSS 类。我花了几个小时来尝试修复它,解决方法是在删除第一个函数后立即添加一个新的、空的或者空白的函数到 touchmove 事件中。这真的非常奇怪。我真的很想知道为什么会发生这种情况。 - oldboy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接