Discovering the true event target of a touchmove JavaScript event

I am currently working on implementing a drag and drop user interface for my web application. The goal is to allow users to drag an item using either a mouse or their finger, and then drop it into one of several designated zones. While the functionality works smoothly with mouse events, I have encountered challenges when dealing with touch events on iPhones and iPads.

The issue arises when the ontouchmove event handler is triggered for an item - the event.touches[0].target always refers back to the original HTML element (the dragged item) instead of the current element under the finger. Additionally, the touchmove handlers for the drop zones do not get activated as the item is dragged over them by a finger. This makes it difficult to determine when a finger is hovering over a drop zone in order to highlight it appropriately. In contrast, when using a mouse, the mousedown event correctly triggers for all elements beneath the cursor.

Some sources suggest that this behavior is characteristic of iPhone touch events, as explained in this article: "For those familiar with traditional web design, the target attribute in mousemove events usually reflects the element currently being hovered over by the mouse. However, in iPhone touch events, the target points back to the initial node where the touch began."

Question: Is there a workaround or method to accurately identify the element currently touched by a finger (as opposed to the originally touched element which may vary)?

Answer №1

It's disappointing to see such unexpected behavior with event targets. This is another example of inconsistencies in the DOM that we may have to live with permanently, thanks to vendors creating hidden extensions without proper scrutiny.

To address this issue, consider utilizing document.elementFromPoint.

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

Answer №2

The solution recommended in a 2010 accepted answer is now outdated as the touchmove event no longer contains clientX or clientY attributes. It seems these attributes were removed in newer versions.

The updated solution is as follows:

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

This solution has been tested and confirmed to work on various platforms including:

  • Safari on iOS
  • Chrome on iOS
  • Chrome on Android
  • Chrome on touch-enabled Windows desktop
  • FF on touch-enabled Windows desktop

However, it has been reported not to work on:

  • IE on touch-enabled Windows desktop

It has not been tested on:

  • Windows Phone

Answer №3

To implement implicit pointer capture, use

event.target.releasePointerCapture(event.pointerId)
in the pointerdown handler.

It's now 2022, and this behavior is intentional and specified - known as "Implicit Pointer Capture".

Refer to the W3 specification on Pointer Events for more information.

Direct manipulation devices should act as if setPointerCapture was called on the target element just before any pointerdown listeners are invoked. You can check whether this has occurred using the hasPointerCapture API (e.g., within a pointerdown listener).

While elementFromPoint is one option, you can also utilize releasePointerCapture, demonstrated in the following example. Holding down on the green div will trigger mouse move events outside of it, while the red div behaves normally.

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>

Answer №4

Touch events and mouse events have different ways of interaction:

  • When the mouse moves, it behaves like a "hover"
  • When touch moves, it behaves like a "drag"

This distinction stems from the fact that a touchmove event cannot occur without a touchstart event preceding it, as the user must physically touch the screen to initiate the interaction. In contrast, with a mouse, a user can move it around the screen without clicking any buttons.

Because of this fundamental difference, hover effects cannot be used effectively with touch devices:

element:hover { 
    background-color: yellow;
}

When a user touches the screen with one finger, the first event (touchstart) captures the target element, while subsequent events (touchmove) maintain reference to the original element where the touch started. This may seem counterintuitive, but it serves the purpose of preserving information about the original target. Ideally, future improvements should provide access to both the source target and current target.

In the current practice (as of 2018), where screens support both mouse and touch input simultaneously, it is common to attach listeners for both types of input and then "normalize" event coordinates. The browser API mentioned above is used to identify the element at those coordinates:

// get coordinates based on pointer type:
var xcoord = event.touches ? event.touches[0].pageX : event.pageX;
var ycoord = event.touches ? event.touches[0].pageY : event.pageY;
// identify the element at specified coordinates:
var targetElement = document.elementFromPoint(xcoord, ycoord);
// check if the element is suitable for our needs:
if (targetElement && targetElement.classList.contains("dropZone")) {
}

Answer №5

In my experience with Android (using WebView + Phonegap), I have encountered a similar issue. My goal is to enable the ability to drag elements and detect when they are dragged over a specific element. Interestingly, touch-events appear to disregard the value of the pointer-events attribute.

Regarding Mouse Events:

  • If pointer-events="visiblePainted" is defined, then event.target will refer to the element being dragged.
  • If pointer-events="none" is specified, then event.target will point to the element beneath the dragged element (the drag-over zone).

This functionality aligns with the intended purpose of the pointer-events attribute.

Touch Events:

  • event.target consistently refers to the dragged element, regardless of the pointer-events value, which appears to be incorrect in my opinion.

To address this issue, I have devised a workaround by creating a custom drag-event object (a unified interface for both mouse and touch events) that includes event coordinates and the target element:

  • For mouse events, I directly utilize the mouse event data.
  • For touch events, I implement the following:

    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)
        };
    };
    

I then utilize this approach for processing (validating if the dragged object is within my drag-over zone). Strangely, it seems that document.elementFromPoint() respects the pointer-events value even on Android.

Answer №6

Unfortunately, JSP64's solution was not entirely effective as the event.originalEvent consistently returned undefined. However, by making a small adjustment as shown below, the issue is resolved.

let touchCoordinates = event.touches[0];
let targetElement = document.elementFromPoint(touchCoordinates.clientX, touchCoordinates.clientY);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Could there be a scenario where the body onload function runs but there is still some unexec

I am feeling confused by a relatively straightforward question. When it comes to the <body> tag being positioned before content, I am wondering about when the body onload function actually runs - is it at the opening tag or the closing tag? Additio ...

Tips on positioning a button "above" the carousel-control-next icon

I have a carousel and I added a button to stop the spinning. However, when I try to click on the button, it seems like the "carousel-control-next" tag is being clicked instead. How can I solve this issue? ** Interestingly, when I remove the "carousel-cont ...

Tips for efficiently rendering large data in nextjs when it comes into view?

Is there a way to create a dropdown option in Nextjs where users can easily select an erc20 token from a token list? I attempted to use a standard mapping function on the token list, but it caused the site to become unresponsive and very slow due to the s ...

Using Json with React's Context API

I am dealing with nested JSON and arrays within it. My goal is to create a Search functionality that will iterate through the arrays and, based on a specific ID, display the name of the corresponding object in the array. I attempted using the Context API ...

Retrieving information from an API and presenting it as a name with a link to the website

I am attempting to retrieve information from an API and present it in the format Name + clickable website link. Although I have managed to display the data, the link appears as text instead of a hyperlink. Here is my Ajax script: $(function() { ...

Inquire: How can I transfer a value from one form to another HTML document, and then retrieve and utilize the value in JavaScript?

My goal in Express is quite complex, as I need to retrieve data from the form located in 'inputdata.html' (with a ROUTE of '/inputdata'). This data will then be passed to 'info.html' (with a ROUTE of '/info') Once ...

How can I use a JavaScript function to remove an element from a PHP array?

Imagine I have a PHP session array: $_SESSION[MyItems]=Array('A'=>"Apple", 'B'=>"Brownie", 'C'="Coin")) which is utilized to showcase items on a user's visited page. I want the user to have the ability to remove o ...

Tips for transferring data to a child component's @Input using Observables

I've created an angular component that serves as a tab within a for loop on my HTML page: ... <ng-container *ngFor="let tabData of data$ | async;"> <tab-component id="{{ tabData.id }}" name="{{ tabData.name }} ...

Troubleshooting: React is not defined in Rollup + React 17 with updated JSX Transform

I'm currently working on prototyping a microfrontend architecture using Rollup and multiple create-react-app applications. However, when I try to locally yarn link my external app with the container app, I am encountering the following error: The err ...

Mastering the art of utilizing clearInterval effectively

This reset button needs to clear/reset the timer. The issue is that when the user presses the start button more than once, it sets two timers - one from the previous start and one from the current start. I will be using clearInterval to solve this problem ...

Injecting attributes from a form into partial components

I have been working on a NodeJS app and have successfully implemented handlebars and partials so far. Currently, I am at a stage where I need to create both 'view' and 'edit' forms for a car. For instance, once the user submits an appl ...

Which method is optimal for organizing tree categories and linking them to posts, as well as locating posts based on a selected category within a MERN stack

Currently, I am utilizing the MERN stack for my project development. The project involves a tree category structure as outlined below: {id: { type: Number }, parent_id: { type: Number }, name: { type: String }, sub: { type: Boolean }} For ...

Transforming ASP.NET MVC IEnumerable view model into a JSON array of elements

My goal is to develop a dynamic calendar in ASP.NET MVC that pulls event data from a database to populate it. Right now, the calendar is set up to read a json array of objects, but I am facing an issue with converting my ViewModel data into a format that t ...

Position object in the middle using jQuery and CSS

Trying to center an absolutely positioned object horizontally using CSS and jQuery is proving to be a challenge. The use of jQuery is necessary due to the varying widths of the objects. Hover over the icons in my jsFiddle to see the issue. Check out the j ...

Having trouble transferring file object from reactjs to nodejs

Hey there! I am relatively new to nodejs and React, and currently, I'm working on a project that involves sending a selected file from the front end (React) to the back end (Node). The goal is to upload the file and convert it into a JSON object. Howe ...

Issue encountered when attempting to remove items from Redux store with onClick event

My goal is to delete a specific object from an array in my store. I have a delete item function that successfully removes objects, but I am struggling to make it work with a button that is rendered with each object using map. Here is my component: import ...

What steps can I take to update this HTML document and implement the DOM 2 Event Model?

I'm struggling with converting my document from the DOM 0 Event model to the DOM 2 Event model standards. I've tried getting help from different tutors on Chegg, but no one seems to have a solution for me. Hoping someone here can assist me!:) P. ...

AngularJS allows for the passing of a function value into another function

Is there a way for me to pass a value from one function to another? Here is an example of what I am trying to achieve: HTML <div> <li ng-repeat="language in languages" ng-click="myFirstFunction(firstValue) {{lang ...

Is the mounted hook not being triggered in a Nuxt component when deploying in production (full static mode)?

I have a component that is embedded within a page in my Nuxt project. This particular component contains the following lifecycle hooks: <script> export default { name: 'MyComponent', created() { alert('hello there!') }, ...

What are the reasons for the various methods available for importing my JS code?

Here is the structure of my folders: --public ----frontend.js --views ----fontend.ejs The frontend.js file is located inside the public folder, while the frontend.ejs file is in the views folder. In my HTML / EJS file, I included the JavaScript (fronten ...