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

IT技术 javascript dom-events
2021-02-05 15:59:16

= 我正在尝试在我的 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-gesting-on-the-iphone/对于那些来自正常的网页设计世界,在正常的 mousemove 事件中,传入 target 属性的节点通常是鼠标当前所在的位置。但在所有 iPhone 触摸事件中,目标是对原始节点的引用。

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

5个回答

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

使用document.elementFromPoint解决它。

document.elementFromPoint(event.clientX, event.clientY);
移动设备的计算量减少了 100 倍,最多可进行 10 次触摸,最多可减少 3 个数量级的障碍。
2021-03-15 15:59:16
我认为很明显这是一种性能妥协。考虑一下移动设备显然需要执行的额外处理,以便每秒 60 次正确确定硬件提供的点下的元素。它几乎需要 DOM 树遍历和 AABB 点检查(无可否认,这可以做得非常快)。我的猜测是,从 Apple 的角度来看,无论事件目标是否按预期工作,缓慢且不稳定的事件性能都不如容易被忽视的错误。
2021-03-18 15:59:16
@StevenLu 哦,真的吗?鼠标悬停事件呢?这不是完全相同的性能妥协吗?我们一直这样,为什么这不同?
2021-04-05 15:59:16
非常感谢你!这就是我所需要的。我还想为那些面临同样问题的人分享更多的魔法:在可拖动元素上添加“pointer-events:none”,使 elementFromPoint 穿过它并抓住真正的目标。
2021-04-06 15:59:16
此外,即使 iOS 原生触摸事件的行为也不会跟踪触摸下方的视图,触摸事件由它最初启动的视图“拥有”,您必须明确编写代码以将事件传递给父和/或覆盖命中测试以找出手指结束的真实实际视图。这一切都是因为,因此不执行发现信息的任务是没有必要的大部分时间的时间。
2021-04-14 15:59:16

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

目前的解决方案是:

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

未测试:

  • 视窗电话
这篇文章需要更多+1
2021-03-16 15:59:16
我没有看到originalEvent关于TouchEventS,在Chrome V52反正。touches可直接在TouchEvent.
2021-03-18 15:59:16
这个答案太神奇了!工作完美,比其他的要简单得多。刚刚在我的标签中添加了一个“数据-...”,现在我确切地知道悬停了什么!
2021-03-26 15:59:16
@ericsoco:对我来说看起来像一些 jquery (originalEvent)。应该这样做: (event.touches && event.touches.length) ?event.touches[0].clientX : event.clientX
2021-03-28 15:59:16
💯检查event instanceof TouchEvent并使用上面的代码。顺便说一句,在 Chrome 上我看到changedTouchesTouchEvent对象,即使 React Typescript 定义不同意。
2021-04-12 15:59:16

我在 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()pointer-events即使在 Android 上似乎也尊重value。

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

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

这种差异来自这样一个事实,即不能在没有 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);