I have a very specific problem. I'm writing a web-page for mobile phones which has a button on it. I'm detecting touchevent on every browser including IE, but on IE it's quite specific. After a few seconds it automatically ends. Can you somehow help me? Here is my code (modified one, but still not working properly):
if (window.navigator.pointerEnabled) {
tapButton.addEventListener("pointerup", function(e) {
e.preventDefault();
addClass(this, 'clicked');
buttonTouched = true;
}, false);
tapButton.addEventListener("pointerdown", function(e) {
e.preventDefault();
removeClass(this, 'clicked');
buttonTouched = false;
}, false);
alert("pointerEnabled");
}
else if (window.navigator.msPointerEnabled) {
tapButton.addEventListener("MSPointerDown", function(e) {
e.preventDefault();
addClass(this, 'clicked');
buttonTouched = true;
}, false);
tapButton.addEventListener("MSPointerUp", function(e) {
e.preventDefault();
removeClass(this, 'clicked');
buttonTouched = false;
}, false);
alert("mspointerEnabled");
}
else {
alert("ordinary touch");
tapButton.addEventListener('touchstart', function(e) {
e.preventDefault();
addClass(this, 'clicked');
buttonTouched = true;
}, false);
tapButton.addEventListener('touchend', function(e) {
e.preventDefault();
removeClass(this, 'clicked');
buttonTouched = false;
}, false);
}
And the html tag has in it:
-ms-touch-action: none !important;
touch-action: none !important;
but that does not help either.
I suspect you are running into a multi-touch issue...
Remember, touch events are not the same as mouse events. You can touch with more than one finger. What happens if you touch with one finger than add a second finger? You get two consecutive touchstart events. The same is probably true for touchend. I suspect user light is right that it's probably triggering a finger release incorrectly...
Please have a look at what is happening to the touches, changedTouches and targetTouches properties of the TouchEvent you get in your listener. I strongly suspect you'll see that there still is a 'finger' left touching... So it went from 2 touches to 1...
Making sure that the (no longer) touching finger is actually the one that's on the button etc is all a lot less simple than the good old mouseup and mousedown events were.
EDIT: I realize your problem is with IE and it's pointer events... However they work mostly the same in that they too support multi-touch (and could thus suffer the same issues). I'm not seeing a property akin to touches, but I do see a pointerId, which can give you the same info (at the cost of some bookkeeping on your end).
This MSDN page has some good info. Especially this code snippet is enlightening I think:
function pointerdownHandler(evt) {
evt.target.setPointerCapture(evt.pointerId);
}
This seems to confirm that, when a finger hits the surface, the contact point gets an ID, which is used to inform you which finger left the surface when you receive the pointerup event.
I'd add some logging that just prints the pointerId on pointerdown and pointerup and I'll bet you will quickly find your solution.
Related
I don't know whether it is only Chrome problem (can't check now), however let's try the following piece of code, where we bind two events to some element:
$("div").on({
mousemove: function(e) {
console.log("move");
},
click: function(e) {
console.log("click");
}
});
If we try to click the element, we'll find that for some reason mousemove event fires immediately after click, so in console we have:
>> ...
>> click
>> move
DEMO: http://jsfiddle.net/gKqVt/
Note, that mousedown and mouseup events work by the same scenario.
I saw many questions on SO about the same problem, but none (in my search) gave the straightforward idea what to do in order to fire the click event only.
Mousemove appears to be binded to every mouse action there is in Chrome, so store the mouse position every time the mouse "moves" and check it against the previous mouse position to validate that it has indeed "moved"..
var currentPos=[];
$("div").on({
mousemove: function(e) {
if (e.pageX!==currentPos[0] && e.pageY !==currentPos[1]){
currentPos=[e.pageX,e.pageY];
this.innerHTML = "Event: " + e.type;
console.log("move");
}
},
click: function(e) {
this.innerHTML = "Event: " + e.type;
console.log("click");
}
});
Demo | Source
This appears to be a bug in Chrome that was first reported back in November, and remains open.
Chromium Issue 161464
If you are targeting Chrome specifically then it may be worth comparing the event timestamps to get around it (using some minimum delta time as #ExplosionPills suggested. But if you're looking for general behavior it seems that you're better off treating them as separate events, because in every browser but chrome (and maybe Safari? the bug is labeled as webkit-core) they will in fact be separate events.
This behavior is odd, and it doesn't seem to occur universally (happens in Chrome/IE for me, but not FFX). I think you haven't gotten a straight answer because there isn't one really.
It's possible that the mouse is moved very slightly by the click action, but that's probably not it. Could just be a browser quirk. These don't even seem to be the same event since stopImmediatePropagation in click doesn't stop mousemove from firing. If you focus the element and hit a keyboard button, it will actually trigger click and only click.
Since this is so quirky, it seems like the only way to deal with it is times. As much of a kludge as this is, I do notice that click happens one millisecond before mousemove, so you could get close by comparing the click timestamp + 2 (or 10):
mousemove: function(e) {
if ($(this).data('lastClick') + 10 < e.timeStamp) {
http://jsfiddle.net/gKqVt/3/
This is very specific, though. You should consider not having behavior that occurs immediately on mousemove since it's so frequent.
Why don't just check that did the mouse really move or not like below:
function onMouseDown (e) {
mouseDown = { x: e.clientX, y: e.clientY };
console.log("click");
}
function onMouseMove (e) {
//To check that did mouse really move or not
if ( e.clientX !== mouseDown.x || e.clientY !== mouseDown.y) {
console.log("move");
}
}
FIDDLE DEMO
(I think it's will still correct in all browsers)
var a,b,c,d;
$(".prd img").on({
mousedown: function(e){
a= e.clientX, b= e.clientY;
},
mouseup: function(e){
c= e.clientX, d= e.clientY;
if(a==c&&b==d){
console.log('clicked');
}
}
});
Try this. This one work correct.
I noticed this behavior when I needed to differenciate between mousedown and mouseup without dragging between the two and mousedown and mouseup with dragging between them, the solution that I used is as follows:
var div = $('#clickablediv');
var mouseDown = false;
var isDragging = 0;
div.mousedown(function () {
isDragging = false;
mouseDown = true;
}).mousemove(function () {
if (mouseDown) isDragging++;
}).mouseup(function () {
mouseDown = false;
var wasDragging = isDragging;
isDragging = 0;
if (!wasDragging || wasDragging<=1) {
console.log('there was no dragging');
}
});
when I tried it, I noticed that periodacaly a simple click makes "isDragging" equal to 3 but not very frequently
I added the following to my mouseMove(event) function:
function mouseMove(event)
{
if ((event.movementX == 0) && (event.movementY == 0)) return;
Not clear why it triggers when there is no movement, but this worked for me. Had the issue in Chrome 102.0.5005.61 at least. It did not happen a few years ago.
I am using contextMenu event in .html sample, it will be fired when i long press on an DIV, but right now it is not working. Is something broken in latest IOS 8.2 version. Here is the sample code,
<head>
<title></title>
<script src="Scripts/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$("#content").on("contextmenu", function () {
alert("CM");
});
});
</script>
</head>
<body>
<div id="content" style="height:300px; width:300px; background-color:gray;"></div>
</body>
Here is the working sample
http://jsfiddle.net/4zu1ckgg/
Please someone help me with this.
Basically, on iOS, touch events are not emulated as mouse events.
Use touch events instead: "touchstart", "touchmove" and "touchend".
In your case, on iOS and contrary to Android, "contextmenu" is not triggered when screen is long touched.
To customize long touch on iOS you should use something like:
// Timer for long touch detection
var timerLongTouch;
// Long touch flag for preventing "normal touch event" trigger when long touch ends
var longTouch = false;
$(touchableElement)
.on("touchstart", function(event){
// Prevent default behavior
event.preventDefault();
// Test that the touch is correctly detected
alert("touchstart event");
// Timer for long touch detection
timerLongTouch = setTimeout(function() {
// Flag for preventing "normal touch event" trigger when touch ends.
longTouch = true;
// Test long touch detection (remove previous alert to test it correctly)
alert("long mousedown");
}, 1000);
})
.on("touchmove", function(event){
// Prevent default behavior
event.preventDefault();
// If timerLongTouch is still running, then this is not a long touch
// (there is a move) so stop the timer
clearTimeout(timerLongTouch);
if(longTouch){
longTouch = false;
// Do here stuff linked to longTouch move
} else {
// Do here stuff linked to "normal" touch move
}
})
.on("touchend", function(){
// Prevent default behavior
event.preventDefault();
// If timerLongTouch is still running, then this is not a long touch
// so stop the timer
clearTimeout(timerLongTouch);
if(longTouch){
longTouch = false;
// Do here stuff linked to long touch end
// (if different from stuff done on long touch detection)
} else {
// Do here stuff linked to "normal" touch move
}
});
Here is a the page explaining (among other) that touch events are not emulated as mouse events on every OS: http://www.html5rocks.com/en/mobile/touchandmouse/
Hope this helps, it took to me a long time to figured it out ;)
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 have 200 photos in my webpage, which has browser scroll and selection for that photos, i implemented shift selection functionality for photo selection. But i have issue in firefox that, when i select photo and press shift key and scroll down mouse the browser is navigating back and forth, Is there any practice to capture both shift + scroll event in firefox.
Thanks in advance
Try this, should work in both Chrome and Firefox.
$(document).on('mousewheel DOMMouseScroll', function(event) {
if (event.shiftKey) {
// do something.
}
})
You must return false in your event handler.
$(document).bind('mousewheel', function (event) {
// do your stuff
return false;
});
I am working with JavaScript and jQuery in an UIWevView on iOS.
I'v added some javascript event handler that allow me to capture a touch-and-hold event to show a message when someone taps an img for some time:
$(document).ready(function() {
var timeoutId = 0;
var messageAppeared = false;
$('img').on('touchstart', function(event) {
event.preventDefault();
timeoutId = setTimeout(function() {
/* Show message ... */
messageAppeared = true;
}, 1000);
}).on('touchend touchcancel', function(event) {
if (messageAppeared) {
event.preventDefault();
} else {
clearTimeout(timeoutId);
}
messageAppeared = false;
});
});
This works well to show the message. I added the two "event.preventDefault();" lines to stop imgs inside links to trigger the link.
The problem is: This also seems to prevent drag events to scroll the page from happen normally, so that the user wouldn't be able to scroll when his swipe happens to begin on an img.
How could I disable the default link action without interfering with scrolling?
You put me on the right track Stefan, having me think the other way around. For anyone still scratching their head over this, here's my solution.
I was trying to allow visitors to scroll through images horizontally, without breaking vertical scrolling. But I was executing custom functionality and waiting for a vertical scroll to happen. Instead, we should allow regular behavior first and wait for a specific gesture to happen like Stefan did.
For example:
$("img").on("touchstart", function(e) {
var touchStart = touchEnd = e.originalEvent.touches[0].pageX;
var touchExceeded = false;
$(this).on("touchmove", function(e) {
touchEnd = e.originalEvent.touches[0].pageX;
if(touchExceeded || touchStart - touchEnd > 50 || touchEnd - touchStart > 50) {
e.preventDefault();
touchExceeded = true;
// Execute your custom function.
}
});
$(this).on("touchend", function(e) {
$(this).off("touchmove touchend");
});
});
So basically we allow default behavior until the horizontal movement exceeds 50 pixels.
The touchExceeded variable makes sure our function still runs if we re-enter the initial < 50 pixel area.
(Note this is example code, e.originalEvent.touches[0].pageX is NOT cross browser compatible.)
Sometimes you have to ask a question on stack overflow to find the answer yourself. There is indeed a solution to my problem, and it's as follows:
$(document).ready(function() {
var timeoutId = 0;
$('img').on('touchstart', function(event) {
var imgElement = this;
timeoutId = setTimeout(function() {
$(imgElement).one('click', function(event) {
event.preventDefault();
});
/* Show message ... */
}, 1000);
}).on('touchend touchcancel', function(event) {
clearTimeout(timeoutId);
});
});
Explanation
No preventDefault() in the touch event handlers. This brings back scrolling behavior (of course).
Handle a normal click event once if the message appeared, and prevent it's default action.
You could look at a gesture library like hammer.js which covers all of the main gesture events across devices.