簡體   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