繁体   English   中英

如何找出touchmove javascript事件的实际event.target?

[英]How to find out the actual event.target of touchmove javascript event?

=我正在尝试在我的 Web 应用程序中开发一个简单的拖放 UI。 可以通过鼠标或手指拖动项目,然后可以将其拖放到多个拖放区之一。 当一个项目被拖过一个放置区域(但尚未释放)时,该区域会突出显示,标记安全着陆位置。 这对鼠标事件非常有效,但我被 iPhone/iPad 上的 touchstart/touchmove/touchend 系列卡住了。

问题在于,当调用项目的 ontouchmove 事件处理程序时,其event.touches[0].target始终指向原始 HTML 元素(项目),而不是当前手指下的元素。 此外,当一个项目被手指拖过某个放置区域时,该放置区域自己的touchmove处理程序根本不会被调用。 这实质上意味着我无法确定手指何时位于任何拖放区上方,因此无法根据需要突出显示它们。 同时,当使用鼠标时, mousedown会针对光标下的所有 HTML 元素正确触发。

有些人确认它应该像那样工作,例如http://www.sitepen.com/blog/2008/07/10/touching-and-gesturing-on-the-iphone/对于那些来自在普通的网页设计世界中,在普通的 mousemove 事件中,目标属性中传递的节点通常是鼠标当前所在的位置。 但在所有 iPhone 触摸事件中,目标都是对原始节点的引用。

问题:有没有办法确定手指下的实际元素(不是最初触摸的元素,在许多情况下可能不同)?

这当然不是事件目标应该如何工作。 另一个 DOM 不一致性,我们现在可能永远都被困住了,因为供应商在没有任何审查的情况下提出了闭门造车的扩展。

使用document.elementFromPoint来解决它。

document.elementFromPoint(event.clientX, event.clientY);

2010 年接受的答案不再有效: touchmove没有clientXclientY属性。 (我猜它曾经有过,因为答案有很多赞成票,但目前没有。)

目前的解决方案是:

var myLocation = event.originalEvent.changedTouches[0];
var realTarget = document.elementFromPoint(myLocation.clientX, myLocation.clientY);

经过测试并适用于:

  • iOS 上的 Safari
  • iOS 上的 Chrome
  • 安卓上的 Chrome
  • 支持触控的 Windows 桌面上的 Chrome
  • 支持触控的 Windows 桌面上的 FF

不适用于:

  • 支持触控的 Windows 桌面上的 IE

未测试:

  • 视窗电话

我在 Android (WebView + Phonegap) 上遇到了同样的问题。 我希望能够拖动元素并检测它们何时被拖动到某个其他元素上。 出于某种原因,触摸事件似乎忽略了pointer-events属性值。

鼠标:

  • 如果设置了pointer-events="visiblePainted" ,则event.target将指向拖动的元素。
  • 如果设置了pointer-events="none" ,则event.target将指向拖动元素下的元素(我的拖动区域)

这就是事情应该如何工作以及为什么我们首先拥有pointer-events属性。

触摸:

  • event.target始终指向拖动的元素,无论pointer-events值是什么,恕我直言是错误的。

我的解决方法是创建我自己的拖动事件对象(鼠标和触摸事件的通用界面)来保存事件坐标和目标:

  • 对于鼠标事件,我只是按原样重用鼠标事件
  • 对于触摸事件,我使用:

     DragAndDrop.prototype.getDragEventFromTouch = function (event) { var touch = event.touches.item(0); return { screenX: touch.screenX, screenY: touch.screenY, clientX: touch.clientX, clientY: touch.clientY, pageX: touch.pageX, pageY: touch.pageY, target: document.elementFromPoint(touch.screenX, touch.screenY) }; };

然后使用它进行处理(检查拖动的对象是否在我的拖动区域中)。 出于某种原因, document.elementFromPoint()似乎甚至在 Android 上也尊重pointer-events值。

因此,当涉及到如何交互时,触摸事件具有不同的“哲学”:

  • 鼠标移动 = “悬停”之类的行为
  • 触摸移动 = “拖动”之类的行为

这种差异来自这样一个事实,即不能在没有 touchstart 事件的情况下进行 touchmove,因为用户必须触摸屏幕才能开始此交互。 使用鼠标,用户当然可以在整个屏幕上移动鼠标而无需按下按钮(mousedown 事件)

这就是为什么基本上我们不能希望使用触摸悬停效果之类的东西:

element:hover { 
    background-color: yellow;
}

这就是为什么当用户用一根手指触摸屏幕时,第一个事件 (touchstart) 获取目标元素,随后的事件 (touchmove) 将保留对触摸开始的原始元素的引用。 感觉不对,但有这种逻辑,您可能也需要原始目标信息。 所以理想情况下,将来应该有(源目标和当前目标)可用。

因此,今天(2018 年)屏幕可以同时是鼠标和触摸的常见做法仍然是附加两个侦听器(鼠标和触摸),然后“标准化”事件坐标并使用上述浏览器 api 在这些坐标中查找元素:

  // get coordinates depending on pointer type:
  var xcoord = event.touches? event.touches[0].pageX : event.pageX;
  var ycoord = event.touches? event.touches[0].pageY : event.pageY;
  // get element in coordinates:
  var targetElement = document.elementFromPoint(xcoord, ycoord);
  // validate if this is a valid element for our case:
  if (targetElement && targetElement.classList.contains("dropZone")) {
  }

JSP64 的答案并没有完全奏效,因为event.originalEvent总是返回undefined 如下稍作修改现在可以工作。

var myLocation = event.touches[0];
var realTarget = document.elementFromPoint(myLocation.clientX, myLocation.clientY);

尝试在 pointerdown 处理程序中使用event.target.releasePointerCapture(event.pointerId)

我们现在是 2022 年,这是预期和指定的行为 - 它被称为“隐式指针捕获”

请参阅 关于指针事件的 W3 规范

直接操作设备的行为应该与在调用任何指针向下侦听器之前在目标元素上调用 setPointerCapture 完全相同。 可以使用 hasPointerCapture API(例如,在任何指针向下监听器中)来确定这是否已经发生。

elementFromPoint 是一种可能的解决方案,但您似乎也可以使用 releasePointerCapture ,如下面的演示所示。 触摸并按住绿色 div 将获得其外部目标的鼠标移动事件,而红色 div 具有默认行为。

 const outputDiv = document.getElementById('output-div'); const releaseDiv = document.getElementById('test-release-div'); const noreleaseDiv = document.getElementById('test-norelease-div'); releaseDiv.addEventListener('pointerdown', function(e) { outputDiv.innerHTML = "releaseDiv-pointerdown"; if (e.target.hasPointerCapture(e.pointerId)) { e.target.releasePointerCapture(e.pointerId); } }); noreleaseDiv.addEventListener('pointerdown', function(e) { outputDiv.innerHTML = "noreleaseDiv-pointerdown"; }); document.addEventListener('pointermove', function(e) { outputDiv.innerHTML = e.target.id; });
 <div id="output-div"></div> <div id="test-release-div" style="width:300px;height:100px;background-color:green;touch-action:none;user-select:none">Touch down here and move around, this releases implicit pointer capture</div> <div id="test-norelease-div" style="width:300px;height:100px;background-color:red;touch-action:none;user-select:none">Touch down here and move around, this doesn't release implicit pointer capture<div>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM