Handling touch-based events - javascript

I currently have this for my touch events:
if( 'ontouchstart' in document.body) {
usevkeys = true;
canvas.addEventListener("touchstart",function(e) {evt.call(this,e);},false);
canvas.addEventListener("touchend",function(e) {evt.call(this,e);},false);
canvas.addEventListener("touchmove",function(e) {evt.call(this,e);},false);
}
else {
canvas.addEventListener("mousemove",function(e) {evt.call(this,e);},false);
canvas.addEventListener("click",function(e) {evt.call(this,e);},false);
}
This works fine on my laptop, and on my phone. However, I have to wonder, how would this react in an environment that has both a touchscreen and a normal mouse? Does the mouse trigger touch events, like the phone triggers mousemove events?
What can I do to make sure it works?

a touchstart triggers the click event as soon as touchend has fired (-> if it hasn't been canceled). So you should just remove the else clause and should be fine!
Microsoft went a different way with their touch events in IE10 to harmonize all pointer-devices into Pointer and gesture events where you can check if it has been fired by a mouse, pen or a finger - or perhaps a kinect-style device in the future.

Related

Javascript Listeners for blur and pointerup working differently between Desktop and Mobile

I have a bit of Javascript that I'm using to delay the execution of some function after a blur event has been triggered by waiting for the next pointerup.
/* anonymous pointer listeners for debugging */
window.addEventListener("pointerdown", () => {console.log("pointerdown");});
window.addEventListener("pointerup", () => {console.log("pointerup");});
const handlePointerUp = () => {
console.log("--- detected pointer up so remove the event listener");
window.removeEventListener("pointerup", handlePointerUp);
}
const handleBlur = () => {
console.log("--- focus lost so listen for next pointer up");
window.addEventListener("pointerup", handlePointerUp);
}
document.getElementById("someButton").addEventListener("blur", handleBlur);
On Desktop this seems to work just fine and I get the console logs in the order that I expect
// first time click on someButton
pointerdown
pointerup
// clicking away from someButton
pointerdown
--- focus lost so listen for next pointer up
pointerup
--- detected pointer up so remove the event listener
On Mobile the order is different
// first time click on someButton
pointerdown
pointerup
// clicking away from someButton
pointerdown
pointerup
--- focus lost so listen for next pointer up
It is only triggering the blur after the touch event has been released and not at the start like it does with a mouse click, in which case it is listening for a pointer up that has already happened.
Is there a way to achieve the same thing as what currently would happen on a Desktop in a way that would work the same on Mobile?
The events for pointer and mouse work differently. Quoting MDN;
The pointerdown event is fired when a pointer becomes active. For mouse, it is fired when the device transitions from no buttons pressed to at least one button pressed. For touch, it is fired when physical contact is made with the digitizer.
https://developer.mozilla.org/en-US/docs/Web/API/Element/pointerdown_event
I believe this is what's causing the effect. You can even emulate this by turning on "Toggle device toolbar" in chrome devtools.

Responding to touchstart and mousedown in desktop and mobile, without sniffing

So, I have a problem. I want to respond to a user pressing the mouse button (on desktop) or touching a div (on mobile). I'm trying to be compatile with evergreen browsers. This is what I tried so far:
listen only to mouseDown event. This works on desktop but doesn't work in mobile if the user is dragging. I want the handler to be called as soon as the user touches the screen, no matter if they're moving their finger in the process.
listen only to touchStart event. This works on mobile and desktop, except for Edge and Safari desktop, which don't support touch events.
listen to both, then preventDefault. This causes a double handler call on Chrome mobile. It seems that touch events are passive to allow uninterrupted scrolling on mobile Chrome, so preventDefualt has no effect on them . What I get is a warning message saying "[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080" in the console, preventDefault is ignored and my event is called twice.
Obviously this can be solved by sniffing touch events, but the net is full of self-righteous rants on how one has to be device-agnostic and that it's dangerous to detect touch events before the user interacted.
So I guess that the question is: is there a way to do what I want to do without sniffing for touch events?
Below my sample React code:
function handler(e) {
console.log('handler called')
e.preventDefault()
}
export default function MyElement() {
return (
<div
onMouseDown={handler}
onTouchStart={handler}
>
Hello
</div>
)
}
It turn out it's not yet possible in React. One workaround is set a flag the first time touchStart it's received.
touchHandler = () => {
this.useTouch = true
this.realHandler()
}
mouseHandler = () => {
if (this.useTouch) return
this.realHandler()
}
With the caveat that the first touchStart can be lost in case of dragging.
Quite disappointing.

Detect if a physical mouse or trackpad is present in css or javascript [duplicate]

I currently use the following test (taken out of Modernizr) to detect touch support:
function is_touch_device() {
var bool;
if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
bool = true;
} else {
injectElementWithStyles(['#media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function(node) {
bool = node.offsetTop === 9;
});
}
return bool;
}
But some devices are both touch and mouse driven, so I want a seperate function to detect if a device has mouse support. What's a good way to do this check?
Ultimately my intention is to be able to do these:
if(is_touch_device())
if(has_mouse_support())
if(is_touch_device() && has_mouse_support())
There's a CSS media just for that!
You can check whether some device has a mouse by getting the value of the pointer CSS media feature:
if (matchMedia('(pointer:fine)').matches) {
// Device has a mouse
}
Because it's CSS you don't even need to use JavaScript:
#media (pointer: fine) {
/* Rules for devices with mouse here */
}
I am currently using the following (jQuery) and I haven't found any flaws yet on specific devices
$(window).bind('mousemove.hasMouse',function(){
$(window).unbind('.hasMouse');
agent.hasMouse=true;
}).bind('touchstart.hasMouse',function(){
$(window).unbind('.hasMouse');
agent.hasMouse=false;
});
Explanation: Mouse devices (also touch screen laptops) first fire mousemove before they can fire touchstart and hasMouse is set to TRUE. Touch devices (also for instance iOS which fires mousemove) FIRST fire touchstart upon click, and then mousemove. Then is why hasMouse will be set to FALSE.
The only catch is that this depends on user interaction, the value will only be correct after mouse move or touchstart so cannot be trusted to use on page load.
As mentioned in the question comments, specifically on https://github.com/Modernizr/Modernizr/issues/869, there is no good answer yet.
Answer by #josemmo is not working for me: on android phone with mouse attached matchMedia('(pointer:fine)').matches does not match.
Fortunately, I've succeeded with another media query: hover.
if (matchMedia('(hover:hover)').matches) {
// Device has a mouse
}
var clickHandler = (isMouseEventSupported('click') ? 'click' : 'touchstart');
function isMouseEventSupported(eventName) {
var element = document.createElement('div');
eventName = 'on' + eventName;
var isSupported = (eventName in element);
if (!isSupported) {
element.setAttribute(eventName, 'return;');
isSupported = typeof element[eventName] == 'function';
}
element = null;
return isSupported;
}
This is code from a friend/coworker of mine and he based it off of: http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
There is no immediate way of knowing, you'll have to wait for a touch event or a mouse event.
Presuming you want to detect either mouse or touch you can do the following: listen for touchstart and mousemove (the latter can fire on touch devices without an actual mouse). Whichever one fires first is 99% bound to be what you're looking for.
This does not take in account devices that actually have both.
document.addEventListener('mousemove', onMouseMove, true)
document.addEventListener('touchstart', onTouchStart, true)
function onTouchStart(){
removeListeners()
// touch detected: do stuff
}
function onMouseMove(){
removeListeners()
// mouse detected: do stuff
}
function removeListeners(){
document.removeEventListener('mousemove', onMouseMove, true)
document.removeEventListener('touchstart', onTouchStart, true)
}
As of 2021 pointerevents is implemented in all major browsers.
It gives you the posibility to dynamically detect pointerdevices mouse, touch and pen.
var is_touch_device=(('ontouchstart' in window)||
(navigator.maxTouchPoints > 0)||
(navigator.msMaxTouchPoints > 0));
var has_mouse_support=false;
document.addEventListener("pointermove", function(evt) {
var pointerType=evt.pointerType;
/*** Safari quirk ***/
if(pointerType==="touch"&&evt.height===117.97119140625
&&evt.height===evt.width)pointerType="mouse";
/*** Safari quirk ***/
has_mouse_support=(pointerType==="mouse");
}
It is of course dependent on the user moving the mousepointer.
Even safari on ipadOS 14.4.2 detects it, if AssistiveTouch is activated! But there seems to be some quirks in pointerType detection there. It detects pointerType as mouse first time the mouse is used and no touch has been performed. But if you later use touch, it will not detect and change to pointerType of mouse, if mouse is used after touch! No surprise!
Edit: After some messing around with ipadOS safari i have discovered that, when mouse is used after touch, the pointerevent width and height are the exact same, which in ipadOS 14.4.2 is 117.97119140625 every time mouse is used. This can be used as a not to reliable workaround. Who knows when they will change the width/height? Another peculiarity with pointermove detection in ipadOS, is that mouse move is only detected on buttom press on mouse.
It is not tested with pen on ipad/iphone. Who knows which quirks this will show?

Listening to mousedown AND touchstart on devices that use touch and a mouse (e.g. Surface) [duplicate]

This question already has answers here:
How to bind 'touchstart' and 'click' events but not respond to both?
(37 answers)
Closed 9 years ago.
So, I've run across an interesting problem while working on a Web application for the Microsoft Surface.
I want to add event listeners for when a user interacts with a DOM element. Now I can do:
if ('ontouchstart' in document.documentElement) {
//Attach code for touch event listeners
document.addEventListener("touchstart" myFunc, false);
} else {
//Attach code for mouse event listeners
document.addEventListener("mousedown" myFunc, false);
}
If the device didn't have a mouse input, this problem would be simple and the above code would work just fine. But the Surface (and many new Windows 8 computers) have BOTH a touch and mouse input. So the above code would only work when the user touched the device. The mouse event listeners would never be attached.
So then I thought, well, I could do this:
if ('ontouchstart' in document.documentElement) {
//Attach code for touch event listeners
document.addEventListener("touchstart" myFunc, false);
}
//Always attach code for mouse event listeners
document.addEventListener("mousedown" myFunc, false);
Devices that don't support touch wouldn't have the events attached, but a device that uses touch will register its handlers. The problem with this though is that myFunc() will be called twice on a touch device:
myFunc() will fire when "touchstart" is raised
Because touch browsers typically go through the cycle touchstart -> touchmove -> touchend -> mousedown -> mousemove -> mouseup -> click, myFunc() will be called again in "mousedown"
I've considered adding code tomyFunc() such that it calls e.preventDefault() but this seems to also block touchend as well as mousedown / mousemove / mouseup on some browsers (link).
I hate doing useragent sniffers, but it seems as if touch browsers have variations in how touch events are implemented.
I must be missing something because it seems that surely these JavaScript implementations were decided with possibility of a browser supporting both a mouse and touch!
For windows 8 you can use the "MSPointerDown " event.
if (window.navigator.msPointerEnabled) {
document.addEventListener("MSPointerDown" myFunc, false);
}
Also add the following to your style:
html {
-ms-touch-action: none; /* Direct all pointer events to JavaScript code. */
}
See http://msdn.microsoft.com/en-us/library/ie/hh673557(v=vs.85).aspx for more info.
Inside myFunc, check the event type. If it's touchstart, then do e.stopPropagation().
function myFunc(e) {
if (e.type === 'touchstart') {
e.stopPropagation();
}
//the rest of your code here
}
EDITED:
If you use jQuery, you will be able to do like this:
var clickEventType=((document.ontouchstart!==null)?'mousedown':'touchstart');
$("a").bind(clickEventType, function() {
//Do something
});
This will fire only one of the bind's event.
Found here: How to bind 'touchstart' and 'click' events but not respond to both?

How do I catch taps but not scrolling in Javascript in Android?

I'm making an Javascript web app and I can't for the life of me get the touchstart event to fire. I get the touchmove and touchend events no problem. This is a problem because as I see it the best way to distinguish between a tap and a scrolling motion is to zero a counter on the touchstart event, update it at touchmove and then compare it at touchend. I'm doing this so I can do some action at the end of tap but not a scroll. For instance, it would be very confusing if a page opened for an item in a listed after you finished scrolling down that list, but it would be nice to be able to tap on an item to open its page.
This is what I have:
// FIXME: this doesn't seem to ever fire
el.addEventListener('touchstart', function(e) {
// make sure that at the start of every touch we're not considered to be moving
alert("Touch starting");
app.__touchMoving = 0;
}, false);
el.addEventListener('touchmove', function(e) {
app.__touchMoving++;
}, false);
el.addEventListener('touchend', function(e) {
alert("Touch ended. We moved beforehand this many times: " + app.__touchMoving);
// if we are moving
if (app.__touchMoving > 0) {
// stop, since we're dragging, not tapping
return false;
}
// else we're no longer moving, so it was a tap
}
I never see the touchstart alert. If I scroll the touchend will fire and app__touchMoving will have some sort of decent value. On a side note, I've noticed that sometimes the touchend will seem to fire multiple times.
Am I missing something basic here? Plenty of people say that this should work just fine on Android (and iPhone) yet the first listener never seems to fire.
Update: I should mention that I've been testing on a Samsung Galaxy S running Android 2.1.
I don't know if u can use it: iScroll

Categories