【问题标题】:How to find out the actual event.target of touchmove javascript event?如何找出touchmove javascript事件的实际event.target?
【发布时间】:2011-04-24 13:08:22
【问题描述】:

=我正在尝试在我的 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 事件中,在 target 属性中传递的节点通常是鼠标当前所在的位置。但在所有 iPhone 触摸事件中,目标都是对原始节点的引用。

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

【问题讨论】:

  • 2017 年 12 月,最新的 Chrome,仍在进行中:-(

标签: javascript dom-events


【解决方案1】:

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

使用document.elementFromPoint 来解决它。

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

【讨论】:

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

我在 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 上也是如此。

【讨论】:

    【解决方案3】:

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

    目前的解决方案是:

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

    经过测试并适用于:

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

    不适用于:

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

    未测试:

    • Windows 手机

    【讨论】:

    • 这篇文章需要更多+1
    • 无论如何,在 Chrome v52 中,我在 TouchEvents 上看不到 originalEventtouches 可直接在 TouchEvent 上获得。
    • @ericsoco:对我来说看起来像是一些 jquery (originalEvent)。应该这样做: (event.touches && event.touches.length) ? event.touches[0].clientX : event.clientX
    • 这个答案太棒了!工作完美,比其他的要简单得多。刚刚在我的标签中添加了一个“数据-...”,现在我确切地知道悬停的是什么!
    • ? 检查event instanceof TouchEvent 并使用上面的代码。顺便说一句,在 Chrome 上,我在 TouchEvent 对象上看到了 changedTouches,即使 React Typescript 定义不同意。
    【解决方案4】:

    因此,触摸事件在交互方式方面有不同的“哲学”:

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

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

    【讨论】:

      【解决方案5】:

      由于event.originalEvent 总是返回undefined,所以JSP64 的答案没有完全起作用。现在可以进行如下轻微修改。

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

      【讨论】:

        猜你喜欢
        • 2020-12-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多