How can you determine if a mouseover event is triggered by a touch on a touchscreen device?

Touchscreen touches on modern browsers trigger mouseover events, sometimes causing unwanted behavior when touch and mouse actions are meant to be separate.

Is there a way to differentiate between a "real" mouseover event from a moving cursor and one triggered by touchscreen behavior?

The event object for both scenarios appears identical, making it challenging to distinguish them. One attempted solution involved modifying mouseover events during touchstart and reverting the changes during touchend, but this approach did not work due to event order.


Similar questions have been asked before, but none provide a satisfactory answer:

  • How to handle mouseover and mouseleave events in Windows 8.1 Touchscreen is unrelated to web pages on browsers
  • JQuery .on(“click”) triggers “mouseover” on touch device involves jQuery and uses an ineffective method based on user agents
  • Preventing touch from generating mouseOver and mouseMove events in Android browser focuses solely on preventing events, not identifying their source
  • Browser handling mouseover event for touch devices causes wrong click event to fire is related to iOS interactions and lacks a suitable solution

A potential workaround involves setting a global variable like window.touchedRecently = true; during touchstart and clearing it after a delay, but this is considered a temporary hack.


It's important to note that assuming touchscreen devices do not have mouse-like cursors can be misleading, as some devices support touch input along with mouse-like pens or mice. For more information, refer to my answer on detecting browser support for mouseover events.

This question does not pertain to jQuery and involves Raphael.js paths, requiring a raw JavaScript solution instead.

Answer №1

Exploring the intricacies of the problem, I found it valuable to delve into the complexities and unique cases that arise in any potential solution.

Challenges to Consider:

1 - Variations in touch event implementations across different devices and browsers. What may work on one device could fail on another due to the diverse ways touch-screen interactions are handled across platforms. The disparities are evident from resources like patrickhlauke which showcase the discrepancies in touch-screen functionality.

2 - Lack of indication in event handlers about their initial triggers. It's true that the event object lacks distinction between mouse events caused by a mouse interaction versus those triggered by a touch gesture, leading to ambiguity in tracking event origins.

3 - Temporal limitations of proposed solutions for universal applicability as current W3C Recommendations provide insufficient guidance on handling touch/click events (https://www.w3.org/TR/touch-events/), perpetuating browser-specific implementations. With no recent updates to the Touch Events standards document in 5 years (https://www.w3.org/standards/history/touch-events), a sustainable fix is not imminent.

4 - Dependence on timeouts in absence of defined transition time from touch event to mouse event underscores the necessity for timeout mechanisms despite uncertainties in the spec, complicating resolution strategies.


Proposed Future Approach:

Future direction might pivot towards utilizing Pointer Events over traditional mouse/touch events for enhanced data accessibility through pointerType (https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events), yet hindered by incongruities in cross-browser support (https://caniuse.com/#search=pointer%20events).


Current Mitigation Strategies:

Given:

  1. The challenge of detecting touchscreen presence ()
  2. Persisting non-touch events on touch-enabled screens

Relying solely on intrinsic mouse event attributes becomes imperative in determining event sources amid inherent complexity. Supplementing browser oversight with personalized event tracking during simultaneous touch and mouse actions emerges as a pragmatic workaround.

Reviewing insights from patrickhlauke, certain patterns emerge:

  1. mouseover consistently precedes subsequent click events mousedown, mouseup, and click - adhering to specified orders detailed in W3C guidelines: https://www.w3.org/TR/touch-events/.
  2. For most setups, mouseover antecedes either pointerover, MSPointerOver, or touchstart events.
  3. Devices initiating event sequences with mouseover must be disregarded as inferring touch event precedence necessitates prior touch event registration.

Incorporating flags during initial touch events and removing them post-click stages can streamline event source identification, albeit vulnerable in select scenarios:

  1. If event.preventDefault obstructs tactile event progression, persisting flagged states could misattribute future clicks as touch-induced.
  2. Displacement of target elements mid-event introduces ambiguities per W3C assertions.

"If the contents of the document have changed during processing of the touch events, then the user agent may dispatch the mouse events to a different target than the touch events."


This ensuing reliance on timeouts stems from challenges delineated in identifying activity disruptions introduced by touch projections and dynamic element relocations during concurrent touch/mouse engagements.

As a fascinating dilemma unfolds, forthcoming alterations will feature tailored code recommendations addressing these intricacies. In the interim, suggestions from @ibowankenobi or @Manuel Otto offer pragmatic avenues for conceptual deliberation.

Answer №2

Here's what we know:

If the user doesn't use a mouse:

  • The mouseover event is triggered directly (within 800ms) after either a touchend or a touchstart (if the user tapped and held).
  • The position of the mouseover event matches the position of the touchstart/touchend events.

If the user uses a mouse/pen:

  • The mouseover event is fired before the touch events, and even if not, the position of the mouseover will not match the touch events' position 99% of the time.

Considering these points, I have created a snippet that adds a flag triggeredByTouch = true to the event if the specified conditions are met. You can also add this behavior to other mouse events or set kill = true to completely discard mouse events triggered by touch.

(function (target){
    var keep_ms = 1000 // how long to keep the touchevents
    var kill = false // whether to kill any mouse events triggered by touch
    var touchpoints = []

    function registerTouch(e){
        var touch = e.touches[0] || e.changedTouches[0]
        var point = {x:touch.pageX,y:touch.pageY}
        touchpoints.push(point)
        setTimeout(function (){
            // remove touchpoint from list after keep_ms
            touchpoints.splice(touchpoints.indexOf(point),1)
        },keep_ms)
    }

    function handleMouseEvent(e){
        for(var i in touchpoints){
            //check if mouseevent's position is (almost) identical to any previously registered touch events' positions
            if(Math.abs(touchpoints[i].x-e.pageX)<2 && Math.abs(touchpoints[i].y-e.pageY)<2){
                //set flag on event
                e.triggeredByTouch = true
                //if wanted, kill the event
                if(kill){
                    e.cancel = true
                    e.returnValue = false
                    e.cancelBubble = true
                    e.preventDefault()
                    e.stopPropagation()
                }
                return
            }
        }
    }

    target.addEventListener('touchstart',registerTouch,true)
    target.addEventListener('touchend',registerTouch,true)

    // which mouse events to monitor
    target.addEventListener('mouseover',handleMouseEvent,true)
    //target.addEventListener('click',handleMouseEvent,true) - uncomment or add others if wanted
})(document)

Test it out:

function onMouseOver(e){
  console.log('triggered by touch:',e.triggeredByTouch ? 'yes' : 'no')
}

(function (target){
var keep_ms = 1000 // how long to keep the touchevents
var kill = false // whether to kill any mouse events triggered by touch
var touchpoints = []

function registerTouch(e){
var touch = e.touches[0] || e.changedTouches[0]
var point = {x:touch.pageX,y:touch.pageY}
touchpoints.push(point)
setTimeout(function (){
// remove touchpoint from list after keep_ms
touchpoints.splice(touchpoints.indexOf(point),1)
},keep_ms)
}

function handleMouseEvent(e){
for(var i in touchpoints){
//check if mouseevent's position is (almost) identical to any previously registered touch events' positions
if(Math.abs(touchpoints[i].x-e.pageX)<2 && Math.abs(touchpoints[i].y-e.pageY)<2){
//set flag on event
e.triggeredByTouch = true
//if wanted, kill the event
if(kill){
e.cancel = true
e.returnValue = false
e.cancelBubble = true
e.preventDefault()
e.stopPropagation()
}
return
}
}
}

target.addEventListener('touchstart',registerTouch,true)
target.addEventListener('touchend',registerTouch,true)

// which mouse events to monitor
target.addEventListener('mouseover',handleMouseEvent,true)
//target.addEventListener('click',handleMouseEvent,true) - uncomment or add others if wanted
})(document)
a{
  font-family: Helvatica, Arial;
  font-size: 21pt;
}
<a href="#" onmouseover="onMouseOver(event)">Click me</a>

Answer №3

Based on information from https://www.html5rocks.com/en/mobile/touchandmouse/
When it comes to a single click, the sequence of events is:

  1. touchstart
  2. touchmove
  3. touchend
  4. mouseover
  5. mousemove
  6. mousedown
  7. mouseup
  8. click

One approach could be setting a boolean variable isFromTouchEvent = true; in onTouchStart() and then changing it to isFromTouchEvent = false; in onClick(). This way, you can check for this variable inside onMouseOver(). However, keep in mind that we may not receive all these events for the element we are monitoring.

Answer №4

My approach involves utilizing a couple of different strategies, one of which relies on a manual implementation of setTimeout to activate a specific property. While I will delve into this method shortly, it is worth considering the usage of touchstart, touchmove, and touchend events for touch devices, as well as mouseover events for desktop interactions.

In situations where event.preventDefault is called (remember that the event must not be passive for this to function with touchstart), any subsequent mouse calls will be canceled automatically, simplifying the handling process. However, if this behavior does not align with your intentions, an alternative solution can be explored using your preferred DOM manipulation library and element references:

Implementing setTimeout

library.select(elem) //select the desired element
.property("_detectTouch",function(){//establish a _detectTouch method to temporarily set a property on the element
    return function(){
        this._touchDetected = true;
        clearTimeout(this._timeout);
        this._timeout = setTimeout(function(self){
            self._touchDetected = false;//adjust accordingly based on your scenario; for combined touch and desktop support, consider setting it between 300-400ms
        },10000,this);
    }
}).on("click",function(){
    /*perform certain actions*/
}).on("mouseover",function(){
    if (this._touchDetected) {
        /*interaction from a touch-based device*/
    } else {
        /*desktop interaction*/
    }
}).on("touchstart",function(){
    this._detectTouch();//execute the property method defined earlier
    toggleClass(document.body,"lock-scroll",true);//disable scrolling by hiding overflow-y on the body element
}).on("touchmove",function(){
    disableScroll();//if the previous overflow-y adjustment fails, use another method to prevent scroll on iOS
}).on("touchend",function(){
    library.event.preventDefault();//ensure event propagation is halted; note: calling this in touchstart may trigger Chrome warnings (unless explicitly declared non-passive)
    this._detectTouch();
    var touchObj = library.event.targetTouches &&& library.event.targetTouches.length 
        ? library.event.targetTouches[0] 
        : library.event.changedTouches[0];
    if (elem.contains(document.elementFromPoint(touchObj.clientX,touchObj.clientY))) {//verify if the touch remains within the designated element
        this.click();//invoke click action since default has been prevented during touchend
    }
    toggleClass(document.body,"lock-scroll",false);//re-enable scrolling functionality
    enableScroll();//restore scrolling capabilities
})

An alternative technique without relying on setTimeout involves treating mouseover as an opposite of touchstart, and mouseout as counter to touchend. By establishing properties through touch events and detecting them in subsequent mouse events, you can create a seamless transition between touch and desktop interactions:

Implementation without setTimeout

....
.on("mouseover",function(dd,ii){
                    if (this._touchStarted) {//triggered by touch device
                        this._touchStarted = false;
                        return;
                    }
                    /*desktop interaction*/
                })
                .on("mouseout",function(dd,ii){//similar concept as above
                    if(this._touchEnded){
                        this._touchEnded = false;
                        return;
                    }
                })
                .on("touchstart",function(dd,ii){
                    this._touchStarted = true;
                    /*perform specific actions*/
                })
                .on("touchend",function(dd,ii){
                    library.event.preventDefault();//ensure emulated events are suppressed completely at this stage, leveraging attached properties for further control
                    this._touchEnded = true;
                    /*perform additional actions*/
                });

While some details have been omitted for brevity, the core concepts remain intact within these implementations.

Answer №5

One solution is to utilize modernizr for this task! After conducting a test on my local development server, I can confirm that it functions as expected.

if (Modernizr.touch) { 
  console.log('Touch Screen');
} else { 
  console.log('No Touch Screen');
} 

Therefore, beginning with modernizr could be a good starting point.

Answer №6

Pointer Events have gained widespread support. This means we can now utilize pointerenter and examine event.pointerType:

const hoverElement = document.getElementById("hoverableArea")

hoverElement.addEventListener("pointerenter", (event) => {
  if (event.pointerType === "mouse") {
    alert("Hover detected")
  }
})
<div id="hoverableArea">Activate on hover, but not on touch</div>

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

The JavaScript counterpart to jQuery's click event handler

I'm trying to figure out how to achieve the same effect as this jQuery code. var divQuery = $('.html5gallery-thumbs-0').children(); divQuery.on('click', function () {...} I attempted it like this: var divQuery = document.g ...

encountering issue alerts when using the MUI TextField module alongside the select function

Encountering an error in the MUI console: children must be provided when using the TextField component with select. <TextField select id="outlined-basic" label="User Name" name="user" size="small" {...teamForm.getFieldProps("user")} erro ...

What is preventing Backbone from triggering a basic route [and executing its related function]?

Presenting My Router: var MyRouter = Backbone.Router.extend({ initialize: function(){ Backbone.history.start({ pushState:true }); }, routes: { 'hello' : 'sayHello' }, sayHello: function(){ al ...

Leveraging Three.js Raycaster for a seamless PDF download functionality

Is there a way to trigger a PDF download when clicking on a 3D object in a Three.js scene? Below is an example of how I have set up the Raycaster: var raycaster; var mouse = { x: 0, y: 0 }; init(); function init() { raycaster = new THREE.Raycaster() ...

Using JavaScript's if-else statements is akin to a checkbox that is always in its

When working with checkboxes, I can retrieve the state (checked or unchecked) in the browser developer console using $("#blackbox").prop('checked')or $('#blackbox').is(':checked'). I have tried both methods. For example, if I ...

Exception in posting strings or JSON with react.js

Whenever a user clicks on the Signup button, the process I follow to create an account is as follows: Firstly, a new User entry is created in the database. createUser = () =>{ var xhr = new XMLHttpRequest(); xhr.open('POST', 'http:// ...

Formik: encountering target as undefined while passing Material UI Date Picker as prop to React Component

I'm currently working on developing a component that can be reused. The idea is to pass form fields as props, but I ran into an issue when clicking on the datepicker field. The error message that pops up is: TypeError: target is undefined Any sugge ...

Does D3 iterate through the entire array every time we define a new callback?

It seems that every time a callback is set, d3 loops through the entire array. Initially, I thought that functions like attr() or each() were added to a pipeline and executed all at once later on. I was trying to dynamically process my data within d3&apo ...

Check if the element is not present in the array, then add it to the array

I am attempting to generate an array in JavaScript with unique elements extracted from a JSON feed. My thought process is possibly too influenced by PHP, where a simple script like the one below would suffice: $uniqueArray = array(); foreach($array as $ke ...

Remove HTML element and launch in a separate browser tab while preserving styles

I am currently working on developing a single-page application with Polymer3 (Javascript ES6 with imports). One of the key functionalities of my application involves moving widgets to new browser windows, allowing users to spread them across multiple scree ...

Can a TypeScript-typed wrapper for localStorage be created to handle mapped return values effectively?

Is it feasible to create a TypeScript wrapper for localStorage with a schema that outlines all the possible values stored in localStorage? Specifically, I am struggling to define the return type so that it corresponds to the appropriate type specified in t ...

Using Jquery to insert an if statement within the .html element

Trying to implement jQuery to replace html content within an object, but struggling with incorporating an if statement. Like this: $(this).html("<select id=\'status\' name=\'status[]\' multiple><option valu ...

Rendering a Component Inside Another Component

I am facing a situation where I have a component named TicketView being used within another component called Table. The initialization of TicketView in the table looks like this: <TableRow key={index}> ... <TableRowColumn style={{width: & ...

Discovering the ways to retrieve Axios response within a SweetAlert2 confirmation dialog

I'm struggling to grasp promises completely even after reviewing https://gist.github.com/domenic/3889970. I am trying to retrieve the response from axios within a sweetalert confirmation dialog result. Here is my current code: axios .post("/post ...

Adding the number of occurrences to duplicates in a string array using JavaScript

Looking to append a count to duplicate entries within a string array. The array in question contains duplicates, as shown below. var myarray = ["John", "John", "John", "Doe", "Doe", "Smith", "John", "Doe", "Joe"]; The desired output shoul ...

Creating a nested JSON file dynamically in Angular: A step-by-step guide

I am looking to dynamically generate a nested JSON file within an Angular project. The data will be extracted from another JSON file, with two nested loops used to read the information. Below is an example of the initial JSON file structure: { "data": [ ...

Display Rails view using JavaScript when clicked

I have a task to create a view where users can click on different buttons and see different content displayed based on the button they choose. I attempted to achieve this using JavaScript, but unfortunately, I couldn't get it to work as intended. Init ...

What is the best method for setting up message scheduling for a Microsoft Teams bot based on different time

I am trying to figure out how to send messages to users from my bot at specific time intervals. Currently, I'm using agenda for scheduling messages, but I've run into an issue with the timezone discrepancy - agenda is 5:30 hours behind my timezon ...

Deciphering JSON data in a textarea following a POST request

I am struggling with decoding a JSON string in PHP and I have identified the problem causing it. However, I am unsure of how to fix it. My approach involves putting an array that I convert to JSON into a hidden textarea, which I then post along with other ...

Tips on displaying information following the parsing of JSON

Trying to retrieve a list of users using this code snippet <div id="users" data-users='[{"name":"one","userName":"user_one"}, {"name":"two","userName":"user_two&q ...