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

Ily*_*nov 45 javascript javascript-events

我正在尝试在我的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/: 对于那些你来自的人在正常的web设计世界中,在正常的mousemove事件中,目标属性中传递的节点通常是鼠标当前所在的节点.但在所有iPhone touch事件中,目标是对原始节点的引用.

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

Gle*_*ard 46

这当然不是事件目标的工作方式.还有另一个DOM不一致,我们现在可能永远都会陷入困境,因为供应商在没有任何审查的情况下提出了闭门造车的扩展.

使用document.elementFromPoint来解决它.

document.elementFromPoint(event.clientX, event.clientY);
Run Code Online (Sandbox Code Playgroud)

  • 非常感谢你!这就是我所需要的.我还想为那些遇到同样问题的人分享更多的魔力:在你的可拖动元素上添加"pointer-events:none",使elementFromPoint通过它落入并捕获真正的目标. (10认同)
  • @StevenLu哦真的吗?鼠标悬停事件怎么样?这不是完全相同的性能折衷吗?我们永远都有这个,为什么会有所不同? (2认同)
  • 移动设备的计算量减少了100倍,最多可达10次触摸,最多可达3个数量级的障碍. (2认同)

JSP*_*P64 34

2010年接受的答案不再有效:touchmove没有clientXclientY属性.(我猜它已经习惯了,因为答案有很多赞成,但目前还没有.)

目前的解决方案是

var myLocation = event.originalEvent.changedTouches[0];
var realTarget = document.elementFromPoint(myLocation.clientX, myLocation.clientY);
Run Code Online (Sandbox Code Playgroud)

经过测试和处理:

  • iOS上的Safari
  • iOS上的Chrome
  • Android上的Chrome
  • 支持触控的Windows桌面上的Chrome
  • 启用触摸的Windows桌面上的FF

不适用于:

  • IE在启用触摸的Windows桌面上

未测试:

  • Windows Phone

  • 无论如何,我没有在TouchEvent`s上看到`originalEvent`,在Chrome v52中.`touches`直接在`TouchEvent`上提供. (3认同)
  • @ericsoco:对我来说看起来像是一些jquery(originalEvent).这样的事情应该这样做:(event.touches && event.touches.length)?event.touches [0] .clientX:event.clientX (3认同)
  • 这篇文章需要更多+1 (2认同)

use*_*859 15

尝试event.target.releasePointerCapture(event.pointerId)在指针向下处理程序中使用。

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

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

直接操作设备的行为应该与在调用任何 pointdown 侦听器之前在目标元素上调用 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;
});
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

  • 这应该是公认的答案。当前接受的答案已过时,会导致性能问题,同时也容易出现错误(如果在当前答案之上还有另一个元素) (3认同)

小智 6

我在 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)
        };
    };
    
    Run Code Online (Sandbox Code Playgroud)

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


Ser*_*nko 5

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

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

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

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

element:hover { 
    background-color: yellow;
}
Run Code Online (Sandbox Code Playgroud)

这就是为什么当用户用一根手指触摸屏幕时,第一个事件 (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")) {
  }
Run Code Online (Sandbox Code Playgroud)