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 have already tried doing
function isTouchDevice() {
return 'ontouchstart' in window;
}
function customBindTest(ele, eventType, callback) {
if ((eventType === 'click') && isTouchDevice()) {
eventType = 'touchend';
}
var onevent = 'on' + eventType;
if (ele.attachEvent) { // IE
ele.attachEvent(onevent, callback);
} else if (ele.addEventListener) {
ele.addEventListener(eventType, callback, false);
} else {
ele[onevent] = callback;
}
}
But the problem I now face is that When on a laptop such as Windows 8 with a touch screen and mouse pad. You can't click via the mouse in browsers such as Chrome (Whereas IE10 is fine because it doesn't have the key ontouchstart.
Has anyone got any ideas or come across any similar issues in the past?
Some time ago i wrote a custom touch/mouse event system witch preserves scrolling and everything else like normal but it adds new events based on he devices.Wich respectively use the mouse to create the swipes & clicks or the touches.it also does all the math outside the mousemove / touchmove to leave this function lagfree.
first i checked which tipe of os i have (in your case you need to change this line to make it work in win8)
(/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)?'touch':'mouse')
then i created an array containing the various functions for mouse and touch events ...
it creates 6 new events.
fc: fastclick (especially for ios devices else the click takes 500ms) 50ms
sfc: superfastclick (this is for fixed elements , its basically on touchstart) 0ms
not tu use when you also scroll.
and the various swipes swl,swr,swu,swd (swipeleft,right,up & down)
the mouse reacts like a normal touch event
if you click->hold->move right->release it gives you a swipe right event.
told that in your case you need both.(wich could be a problem... dunno)
but you can do that just by deleting this line (also remove the device check mentioned on top)
for(var a in f[m]){d.addEventListener(a,f[m][a],false);}
and replace it with
for(var a in f['mouse']){d.addEventListener(a,f['mouse'][a],false);}
for(var a in f['touch']){d.addEventListener(a,f['touch'][a],false);}
i can't test it on win 8 touch ... try it.
http://jsfiddle.net/3u5pJ/
To test just swipe or click on the result box looking at the console.
ps: As ie 10 is standardized everything should work like in all new modern browsers.this code is not meant to be working on obsolete browsers.
EDIT
this example adds touch events on the mouse and the touches.
http://jsfiddle.net/3u5pJ/1/
To test just swipe or click on the result box looking at the console.
yeah and if you use that code u should not use click events as they are very bad in touch devices , but use the custom fc or sfc.
usage:
element.addEventListener('swl',handlerFunction,false)//swipeleft
if you don't understand something just ask.
Related
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?
I am working on some javascript UI, and using a lot of touch events like 'touchend' for improved response on touch devices. However, there are some logical issues which are bugging me ...
I have seen that many developers mingle 'touchend' and 'click' in the same event. In many cases it will not hurt, but essentially the function would fire twice on touch devices:
button.on('click touchend', function(event) {
// this fires twice on touch devices
});
It has been suggested that one could detect touch capability, and set the event appropriately for example:
var myEvent = ('ontouchstart' in document.documentElement) ? 'touchend' : 'click';
button.on(myEvent, function(event) {
// this fires only once regardless of device
});
The problem with the above, is that it will break on devices that support both touch and mouse. If the user is currently using mouse on a dual-input device, the 'click' will not fire because only 'touchend' is assigned to the button.
Another solution is to detect the device (e.g. "iOS") and assign an event based on that:
Click event called twice on touchend in iPad.
Of course, the solution in the link above is only for iOS (not Android or other devices), and seems more like a "hack" to solve something quite elementary.
Another solution would be to detect mouse-motion, and combine it with touch-capability to figure out if the user is on mouse or touch. Problem of course being that the user might not be moving the mouse from when you want to detect it ...
The most reliable solution I can think of, is to use a simple debounce function to simply make sure the function only triggers once within a short interval (for example 100ms):
button.on('click touchend', $.debounce(100, function(event) {
// this fires only once on all devices
}));
Am I missing something, or does anyone have any better suggestions?
Edit: I found this link after my post, which suggests a similar solution as the above:
How to bind 'touchstart' and 'click' events but not respond to both?
After a day of research, I figured the best solution is to just stick to click and use https://github.com/ftlabs/fastclick to remove the touch delay. I am not 100% sure this is as efficient as touchend, but not far from at least.
I did figure out a way to disable triggering events twice on touch by using stopPropagation and preventDefault, but this is dodgy as it could interfere with other touch gestures depending on the element where it is applied:
button.on('touchend click', function(event) {
event.stopPropagation();
event.preventDefault();
// this fires once on all devices
});
I was in fact looking for a solution to combine touchstart on some UI elements, but I can't see how that can be combined with click other than the solution above.
This question is answered but maybe needs to be updated.
According to a notice from Google, there will be no 300-350ms delay any more if we include the line below in the <head> element.
<meta name="viewport" content="width=device-width">
That's it! And there will be no difference between click and touch event anymore!
Yes disabling double-tap zoom (and hence the click delay) is usually the best option. And we finally have good advice for doing this that will soon work on all browsers.
If, for some reason, you don't want to do that. You can also use UIEvent.sourceCapabilities.firesTouchEvents to explicitly ignore the redundant click. The polyfill for this does something similar to your debouncing code.
Hello you can implement the following way.
function eventHandler(event, selector) {
event.stopPropagation(); // Stop event bubbling.
event.preventDefault(); // Prevent default behaviour
if (event.type === 'touchend') selector.off('click'); // If event type was touch turn off clicks to prevent phantom clicks.
}
// Implement
$('.class').on('touchend click', function(event) {
eventHandler(event, $(this)); // Handle the event.
// Do somethings...
});
Your debounce function will delay handling of every click for 100 ms:
button.on('click touchend', $.debounce(100, function(event) {
// this is delayed a minimum of 100 ms
}));
Instead, I created a cancelDuplicates function that fires right away, but any subsequent calls within 10 ms will be cancelled:
function cancelDuplicates(fn, threshhold, scope) {
if (typeof threshhold !== 'number') threshhold = 10;
var last = 0;
return function () {
var now = +new Date;
if (now >= last + threshhold) {
last = now;
fn.apply(scope || this, arguments);
}
};
}
Usage:
button.on('click touchend', cancelDuplicates(function(event) {
// This fires right away, and calls within 10 ms after are cancelled.
}));
For me using 'onclick' in the html element itself, worked for both touch and click.
<div onclick="cardClicked(this);">Click or Touch Me</div>
In iOS 7 Safari there are now two ways to navigate back/forward -- using the traditional back/forward button arrows at the bottom or by swiping from the edge of the screen. I'm using an animation to transition between pages in my ajax app, but I don't want to fire that transition if users are navigating via the edge swipe, because that is an animation itself.
However, the popstate event objects appear to be identical for both types of navigation -- is there any way to differentiate between these two types of user navigations so we can respond accordingly?
UPDATE: I was able to use (what appears to be) a bug in iOS7 Safari to detect correctly the edge swipe vs. back button tap. The bug is that the touchend event is not triggered (until the next touch event) when using the edge swipe (but touchstart and touchmove are). So I set a shouldAnimate flag and disable it on touchmove -- then if the flag is disabled and the popstate occurs, I know it's an edge swipe.
It's correct 99% of the time -- the only time where it could potentially fail is when a user edge-swipes partially and but then lets go and lets the current page snap back into place (at which point my flag would still be disabled) and then taps the back button (which fires no touch events). To handle that last [edge] case I set a timer on touchmove to re-enable the flag after 50ms.
Yes it's "dirty" but for now it gets me what I want in almost every case so I'm ok with it -- until Apple fixes the bug, but hopefully they'll also provide an indicator in the popstate event object that tells us what kind of navigation it is.
You can track edge drag navigation by monitoring the touch events. If the user starts dragging within a certain threshold of the edge of their screen, it will trigger an edge drag navigation transition.
I wrote an extended explanation of how to monitor and act on this using React code here: https://gist.github.com/MartijnHols/709965559cbdb6b241c12e5866941e69. The essential detection part can be achieved in regular JavaScript, like so:
window.isEdgeDragNavigating = false
const IOS_EDGE_DRAG_NAVIGATION_THRESHOLD = 25
let timer
const handleTouchStart = (e) => {
if (
e.touches[0].pageX > IOS_EDGE_DRAG_NAVIGATION_THRESHOLD &&
e.touches[0].pageX <
window.innerWidth - IOS_EDGE_DRAG_NAVIGATION_THRESHOLD
) {
return
}
window.isEdgeDragNavigating = true
if (timer) {
clearTimeout(timer)
}
}
const handleTouchEnd = () => {
timer = setTimeout(() => window.isEdgeDragNavigating = false, 200)
}
document.addEventListener('touchstart', handleTouchStart)
document.addEventListener('touchend', handleTouchEnd)
Short and sad answer: No. This back/forward-swipes are not propagated to the actual page but happen on an OS-level.
I ran into an issue that may be a bug in Chrome, but I was hoping some more seasoned developers could take a look at the problem. I'm using the dom-drag JavaScript library by youngpup (https://github.com/aboodman/dom-drag/blob/gh-pages/dom-drag.js) and noticed that it's not functioning correctly in Chrome. The error is occurring on line 86.
For some reason Chrome is registering a document.onmousemove event even if the mouse has not been moved. I've tried it on every other browser and Chrome is the only one causing the the event to be triggered when a user single clicks. Would this be considered a bug and if so, could anyone recommend a workaround to resolve the issue?
I was using drag.js, and I faced the same problem. I can see that from the link that PlantTheldea gave 2 years ago, chrome didn't fixed it.
In my side, I had draggable items in my DOM that should accept the click event handler. But onmousemove event was triggered during click event if the mouse was not moving. This bug was happening with Chrome.
So I suppose that other people may face the same issue. Here is how I solved it with drag.js:
1 - Go to your onmousemove handler
2 - Check if there are movement in X or Y. If there is any movement then you return true.
Example with the handler of drag.js that I edited:
// onmousemove handler for the document level
// activated after left mouse button is pressed on draggable element
handler_onmousemove = function (e) {
if(e.movementX === 0 && e.movementY == 0){
// No movement detected
return true; // We can continue with other handlers as click
}
Hope it can help.
I'm using the below code to bind "click" or "touchstart" events (using jQuery's on(eventType, function() { ... })).
var what = (navigator.userAgent.match(/iPad/i)) ? 'touchstart' : 'click';
Later on:
$foo.on(what, function() { ... });
... which works great for iPad and "everything else", but I'm concerned that the above code has "iPad tunnel vision"...
My question(s):
Do all other devices (for example, Android tablets) have similarly named "touchstart" events? If so, how can I improve the above code so that I can account for those event types?
In other words, how can I account for a wider range of touch devices in above code (not just iPad)?
EDIT #1
What do you folks think about this:
var foo = ('ontouchstart' in window) ? 'touchstart' : ((window.DocumentTouch && document instanceof DocumentTouch) ? 'tap' : 'click');
Note: Most of the above logic is from Modernizr here.
The above appears to work for Firefox/iPad... I don't have much else to test on at this time.
What I like about the above is that I'm not UA sniffing. :)
Is tap a good default for all other touch devices?
EDIT #2
Some interesting information here, which links to this:
Creating Fast Buttons for Mobile Web Applications
Not a direct answer really, but gives a lot of details of the situation devs face when facing click-related events for multiple platforms and devices.
EDIT #3
Some good info here too:
Android and iPhone touch events
Android and iPhone versions of WebKit have some touch events in common:
touchstart - triggered when a touch is initiated. Mouse equivalent - mouseDown
touchmove - triggered when a touch moves. Mouse equivalent - mouseMove
touchend - triggered when a touch ends. Mouse equivalent - mouseUp. This one is a bit special on the iPhone - see below
touchcancel - bit of a mystery
After reading that, I think I'll change the code above to this:
var foo = (('ontouchstart' in window) || (window.DocumentTouch && document instanceof DocumentTouch)) ? 'touchstart' : 'click';
When I first asked my question - not having access to anything other than an iPad/iPhone - I assumed touchstart was an iOS-specific event; it now looks like touchstart and click will cover most, if not all, of the bases for touch devices.
August 2014 update:
If it's of any help, I've posted some utility classes here:
mhulse / no-x.js:
[no-js] [no-touch] JavaScript utilities to put in of HTML templates that will add js or touch classes for use in CSS and/or JS.
I would strongly suggest against using UA in order to determine whether you're under touch environment.
Use Modernizr instead: http://modernizr.com/
The below code would recognize anything but windows phone 7 because the windows phone 7 does not fire the regular browser touch events. However WP8 would most probably be recognized correctly.
if (Modernizr.touch){
// bind to touchstart, touchmove, etc and watch `event.streamId`
} else {
// bind to normal click, mousemove, etc
}
This may work for you:
var clickEvent = ((document.ontouchstart!==null)?'click':'touchstart');
$("#mylink").on(clickEvent, myClickHandler);
Both iOS and Android have touch events, but Windows uses MSPointer events in IE. If you want a cross-device solution, try pointer.js or learn from it:
https://github.com/borismus/pointer.js