Related
I'm working on a mobile web site that has to work on a variety of devices. The ones giving me a headache at the moment are BlackBerry.
We need to support both keyboard clicks as well as touch events.
Ideally I'd just use:
$thing.click(function(){...})
but the issue we're running into is that some of these blackberry devices have a very annoying delay from the time of the touch to it triggering a click.
The remedy is to instead use touchstart:
$thing.bind('touchstart', function(event){...})
But how do I go about binding both events, but only firing one? I still need the click event for keyboard devices, but of course, don't want the click event firing if I'm using a touch device.
A bonus question: Is there anyway to do this and additionally accommodate browsers that don't even have a touchstart event? In researching this, it looks like BlackBerry OS5 doesn't support touchstart so will also need to rely on click events for that browser.
ADDENDUM:
Perhaps a more comprehensive question is:
With jQuery, is it possible/recommended to handle both touch interactions and mouse interactions with the same bindings?
Ideally, the answer is yes. If not, I do have some options:
We use WURFL to get device info so could create our own matrix of devices. Depending on the device, we'll use touchstart OR click.
Detect for touch support in the browser via JS (I need to do some more research on that, but it seems like that is doable).
However, that still leaves one issue: what about devices that support BOTH. Some of the phones we support (namely the Nokias and BlackBerries) have both touch screens and keyboards. So that kind of takes me full circle back to the original question...is there a way to allow for both at once somehow?
Update: Check out the jQuery Pointer Events Polyfill project which allows you to bind to "pointer" events instead of choosing between mouse & touch.
Bind to both, but make a flag so the function only fires once per 100ms or so.
var flag = false;
$thing.bind('touchstart click', function(){
if (!flag) {
flag = true;
setTimeout(function(){ flag = false; }, 100);
// do something
}
return false
});
This is the fix that I "create" and it take out the GhostClick and implements the FastClick. Try on your own and let us know if it worked for you.
$(document).on('touchstart click', '.myBtn', function(event){
if(event.handled === false) return
event.stopPropagation();
event.preventDefault();
event.handled = true;
// Do your magic here
});
You could try something like this:
var clickEventType=((document.ontouchstart!==null)?'click':'touchstart');
$("#mylink").bind(clickEventType, myClickHandler);
Usually this works as well:
$('#buttonId').on('touchstart click', function(e){
e.stopPropagation(); e.preventDefault();
//your code here
});
Just adding return false; at the end of the on("click touchstart") event function can solve this problem.
$(this).on("click touchstart", function() {
// Do things
return false;
});
From the jQuery documentation on .on()
Returning false from an event handler will automatically call event.stopPropagation() and event.preventDefault(). A false value can also be passed for the handler as a shorthand for function(){ return false; }.
I had to do something similar. Here is a simplified version of what worked for me. If a touch event is detected, remove the click binding.
$thing.on('touchstart click', function(event){
if (event.type == "touchstart")
$(this).off('click');
//your code here
});
In my case the click event was bound to an <a> element so I had to remove the click binding and rebind a click event which prevented the default action for the <a> element.
$thing.on('touchstart click', function(event){
if (event.type == "touchstart")
$(this).off('click').on('click', function(e){ e.preventDefault(); });
//your code here
});
I succeeded by the following way.
Easy Peasy...
$(this).on('touchstart click', function(e){
e.preventDefault();
//do your stuff here
});
I believe the best practice is now to use:
$('#object').on('touchend mouseup', function () { });
touchend
The touchend event is fired when a touch point is removed from the touch surface.
The touchend event will not trigger any mouse events.
mouseup
The mouseup event is sent to an element when the mouse pointer is over the element, and the mouse button is released. Any HTML element can receive this event.
The mouseup event will not trigger any touch events.
EXAMPLE
$('#click').on('mouseup', function () { alert('Event detected'); });
$('#touch').on('touchend', function () { alert('Event detected'); });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1 id="click">Click me</h1>
<h1 id="touch">Touch me</h1>
EDIT (2017)
As of 2017, browsers starting with Chrome are making steps towards making the click event .on("click") more compatible for both mouse and touch by eliminating the delay generated by tap events on click requests.
This leads to the conclusion that reverting back to using just the click event would be the simplest solution moving forward.
I have not yet done any cross browser testing to see if this is practical.
check fast buttons and chost clicks from google https://developers.google.com/mobile/articles/fast_buttons
Well... All of these are super complicated.
If you have modernizr, it's a no-brainer.
ev = Modernizr.touch ? 'touchstart' : 'click';
$('#menu').on(ev, '[href="#open-menu"]', function(){
//winning
});
Generally you don't want to mix the default touch and non-touch (click) api. Once you move into the world of touch it easier to deal only with the touch related functions. Below is some pseudo code that would do what you want it to.
If you connect in the touchmove event and track the locations you can add more items in the doTouchLogic function to detect gestures and whatnot.
var touchStartTime;
var touchStartLocation;
var touchEndTime;
var touchEndLocation;
$thing.bind('touchstart'), function() {
var d = new Date();
touchStartTime = d.getTime();
touchStartLocation = mouse.location(x,y);
});
$thing.bind('touchend'), function() {
var d = new Date();
touchEndTime= d.getTime();
touchEndLocation= mouse.location(x,y);
doTouchLogic();
});
function doTouchLogic() {
var distance = touchEndLocation - touchStartLocation;
var duration = touchEndTime - touchStartTime;
if (duration <= 100ms && distance <= 10px) {
// Person tapped their finger (do click/tap stuff here)
}
if (duration > 100ms && distance <= 10px) {
// Person pressed their finger (not a quick tap)
}
if (duration <= 100ms && distance > 10px) {
// Person flicked their finger
}
if (duration > 100ms && distance > 10px) {
// Person dragged their finger
}
}
Another implementation for better maintenance. However, this technique will also do event.stopPropagation (). The click is not caught on any other element that clicked for 100ms.
var clickObject = {
flag: false,
isAlreadyClicked: function () {
var wasClicked = clickObject.flag;
clickObject.flag = true;
setTimeout(function () { clickObject.flag = false; }, 100);
return wasClicked;
}
};
$("#myButton").bind("click touchstart", function (event) {
if (!clickObject.isAlreadyClicked()) {
...
}
}
Just for documentation purposes, here's what I've done for the fastest/most responsive click on desktop/tap on mobile solution that I could think of:
I replaced jQuery's on function with a modified one that, whenever the browser supports touch events, replaced all my click events with touchstart.
$.fn.extend({ _on: (function(){ return $.fn.on; })() });
$.fn.extend({
on: (function(){
var isTouchSupported = 'ontouchstart' in window || window.DocumentTouch && document instanceof DocumentTouch;
return function( types, selector, data, fn, one ) {
if (typeof types == 'string' && isTouchSupported && !(types.match(/touch/gi))) types = types.replace(/click/gi, 'touchstart');
return this._on( types, selector, data, fn);
};
}()),
});
Usage than would be the exact same as before, like:
$('#my-button').on('click', function(){ /* ... */ });
But it would use touchstart when available, click when not. No delays of any kind needed :D
I just came up with the idea to memorize if ontouchstart was ever triggered. In this case we are on a device which supports it and want to ignore the onclick event. Since ontouchstart should always be triggered before onclick, I'm using this:
<script> touchAvailable = false; </script>
<button ontouchstart="touchAvailable=true; myFunction();" onclick="if(!touchAvailable) myFunction();">Button</button>
You could try like this:
var clickEvent = (('ontouchstart' in document.documentElement)?'touchstart':'click');
$("#mylink").on(clickEvent, myClickHandler);
In my case this worked perfectly:
jQuery(document).on('mouseup keydown touchend', function (event) {
var eventType = event.type;
if (eventType == 'touchend') {
jQuery(this).off('mouseup');
}
});
The main problem was when instead mouseup I tried with click, on touch devices triggered click and touchend at the same time, if i use the click off, some functionality didn't worked at all on mobile devices. The problem with click is that is a global event that fire the rest of the event including touchend.
This worked for me, mobile listens to both, so prevent the one, which is the touch event. desktop only listen to mouse.
$btnUp.bind('touchstart mousedown',function(e){
e.preventDefault();
if (e.type === 'touchstart') {
return;
}
var val = _step( _options.arrowStep );
_evt('Button', [val, true]);
});
This hasn't been mentioned here, but you may want to check out this link: https://joshtronic.com/2015/04/19/handling-click-and-touch-events-on-the-same-element/
To recap for posterity, instead of trying to assign to both handlers and then sort out the result, you can simply check if the device is a touchscreen or not and only assign to the relevant event. Observe:
var clickEvent = (function() {
if ('ontouchstart' in document.documentElement === true)
return 'touchstart';
else
return 'click';
})();
// and assign thusly:
el.addEventListener( clickEvent, function( e ){
// things and stuff
});
I am using this to bind my events so that I can test on touchscreens that handle both touchstart and click events which would fire twice, and on my development PC which only hears the click
One problem the author of that link mentions though, is touchscreen laptops designed to handle both events:
I learned about a third device I was not considering, the touchscreen laptop. It’s a hybrid device that supports both touch and click events. Binding one event means only that event be supported. Does that mean someone with a touchscreen and mouse would have to explicitly touch because that’s the only event I am handling?
Binding touchstart and click seemed ideal to handle these hybrid devices. To keep the event from firing twice, I added e.stopPropagation() and e.preventDefault() to the callback functions. e.stopPropagation() stops events from “bubbling up” to their parents but also keeps a second event from firing. I included e.preventDefault() as a “just in case” but seems like it could be omitted.
Being for me the best answer the one given by Mottie, I'm just trying to do his code more reusable, so this is my contribution:
bindBtn ("#loginbutton",loginAction);
function bindBtn(element,action){
var flag = false;
$(element).bind('touchstart click', function(e) {
e.preventDefault();
if (!flag) {
flag = true;
setTimeout(function() {
flag = false;
}, 100);
// do something
action();
}
return false;
});
I am also working on an Android/iPad web app, and it seems that if only using "touchmove" is enough to "move components" ( no need touchstart ).
By disabling touchstart, you can use .click(); from jQuery. It's actually working because it hasn't be overloaded by touchstart.
Finally, you can binb .live("touchstart", function(e) { e.stopPropagation(); }); to ask the touchstart event to stop propagating, living room to click() to get triggered.
It worked for me.
There are many things to consider when trying to solve this issue. Most solutions either break scrolling or don't handle ghost click events properly.
For a full solution see https://developers.google.com/mobile/articles/fast_buttons
NB: You cannot handle ghost click events on a per-element basis. A delayed click is fired by screen location, so if your touch events modify the page in some way, the click event will be sent to the new version of the page.
It may be effective to assign to the events 'touchstart mousedown' or 'touchend mouseup' to avoid undesired side-effects of using click.
Taking advantage of the fact that a click will always follow a touch event, here is what I did to get rid of the "ghost click" without having to use timeouts or global flags.
$('#buttonId').on('touchstart click', function(event){
if ($(this).data("already")) {
$(this).data("already", false);
return false;
} else if (event.type == "touchstart") {
$(this).data("already", true);
}
//your code here
});
Basically whenever an ontouchstart event fires on the element, a flag a set and then subsequently removed (and ignored), when the click comes.
Why not use the jQuery Event API?
http://learn.jquery.com/events/event-extensions/
I've used this simple event with success. It's clean, namespaceable and flexible enough to improve upon.
var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
var eventType = isMobile ? "touchstart" : "click";
jQuery.event.special.touchclick = {
bindType: eventType,
delegateType: eventType
};
If you are using jQuery the following worked pretty well for me:
var callback; // Initialize this to the function which needs to be called
$(target).on("click touchstart", selector, (function (func){
var timer = 0;
return function(e){
if ($.now() - timer < 500) return false;
timer = $.now();
func(e);
}
})(callback));
Other solutions are also good but I was binding multiple events in a loop and needed the self calling function to create an appropriate closure. Also, I did not want to disable the binding since I wanted it to be invoke-able on next click/touchstart.
Might help someone in similar situation!
For simple features, just recognize touch or click I use the following code:
var element = $("#element");
element.click(function(e)
{
if(e.target.ontouchstart !== undefined)
{
console.log( "touch" );
return;
}
console.log( "no touch" );
});
This will return "touch" if the touchstart event is defined and "no touch" if not. Like I said this is a simple approach for click/tap events just that.
I am trying this and so far it works (but I am only on Android/Phonegap so caveat emptor)
function filterEvent( ob, ev ) {
if (ev.type == "touchstart") {
ob.off('click').on('click', function(e){ e.preventDefault(); });
}
}
$('#keypad').on('touchstart click', '.number, .dot', function(event) {
filterEvent( $('#keypad'), event );
console.log( event.type ); // debugging only
... finish handling touch events...
}
I don't like the fact that I am re-binding handlers on every touch, but all things considered touches don't happen very often (in computer time!)
I have a TON of handlers like the one for '#keypad' so having a simple function that lets me deal with the problem without too much code is why I went this way.
Try to use Virtual Mouse (vmouse) Bindings from jQuery Mobile.
It's virtual event especially for your case:
$thing.on('vclick', function(event){ ... });
http://api.jquerymobile.com/vclick/
Browser support list: http://jquerymobile.com/browser-support/1.4/
EDIT: My former answer (based on answers in this thread) was not the way to go for me. I wanted a sub-menu to expand on mouse enter or touch click and to collapse on mouse leave or another touch click. Since mouse events normally are being fired after touch events, it was kind of tricky to write event listeners that support both touchscreen and mouse input at the same time.
jQuery plugin: Touch Or Mouse
I ended up writing a jQuery plugin called "Touch Or Mouse" (897 bytes minified) that can detect whether an event was invoked by a touchscreen or mouse (without testing for touch support!). This enables the support of both touchscreen and mouse at the same time and completely separate their events.
This way the OP can use touchstart or touchend for quickly responding to touch clicks and click for clicks invoked only by a mouse.
Demonstration
First one has to make ie. the body element track touch events:
$(document.body).touchOrMouse('init');
Mouse events our bound to elements in the default way and by calling $body.touchOrMouse('get', e) we can find out whether the event was invoked by a touchscreen or mouse.
$('.link').click(function(e) {
var touchOrMouse = $(document.body).touchOrMouse('get', e);
if (touchOrMouse === 'touch') {
// Handle touch click.
}
else if (touchOrMouse === 'mouse') {
// Handle mouse click.
}
}
See the plugin at work at http://jsfiddle.net/lmeurs/uo4069nh.
Explanation
This plugin needs to be called on ie. the body element to track touchstart and touchend events, this way the touchend event does not have to be fired on the trigger element (ie. a link or button). Between these two touch events this plugin considers any mouse event to be invoked by touch.
Mouse events are fired only after touchend, when a mouse event is being fired within the ghostEventDelay (option, 1000ms by default) after touchend, this plugin considers the mouse event to be invoked by touch.
When clicking on an element using a touchscreen, the element gains the :active state. The mouseleave event is only fired after the element loses this state by ie. clicking on another element. Since this could be seconds (or minutes!) after the mouseenter event has been fired, this plugin keeps track of an element's last mouseenter event: if the last mouseenter event was invoked by touch, the following mouseleave event is also considered to be invoked by touch.
Here's a simple way to do it:
// A very simple fast click implementation
$thing.on('click touchstart', function(e) {
if (!$(document).data('trigger')) $(document).data('trigger', e.type);
if (e.type===$(document).data('trigger')) {
// Do your stuff here
}
});
You basically save the first event type that is triggered to the 'trigger' property in jQuery's data object that is attached to the root document, and only execute when the event type is equal to the value in 'trigger'. On touch devices, the event chain would likely be 'touchstart' followed by 'click'; however, the 'click' handler won't be executed because "click" doesn't match the initial event type saved in 'trigger' ("touchstart").
The assumption, and I do believe it's a safe one, is that your smartphone won't spontaneously change from a touch device to a mouse device or else the tap won't ever register because the 'trigger' event type is only saved once per page load and "click" would never match "touchstart".
Here's a codepen you can play around with (try tapping on the button on a touch device -- there should be no click delay): http://codepen.io/thdoan/pen/xVVrOZ
I also implemented this as a simple jQuery plugin that also supports jQuery's descendants filtering by passing a selector string:
// A very simple fast click plugin
// Syntax: .fastClick([selector,] handler)
$.fn.fastClick = function(arg1, arg2) {
var selector, handler;
switch (typeof arg1) {
case 'function':
selector = null;
handler = arg1;
break;
case 'string':
selector = arg1;
if (typeof arg2==='function') handler = arg2;
else return;
break;
default:
return;
}
this.on('click touchstart', selector, function(e) {
if (!$(document).data('trigger')) $(document).data('trigger', e.type);
if (e.type===$(document).data('trigger')) handler.apply(this, arguments);
});
};
Codepen: http://codepen.io/thdoan/pen/GZrBdo/
I'm working on a mobile web site that has to work on a variety of devices. The ones giving me a headache at the moment are BlackBerry.
We need to support both keyboard clicks as well as touch events.
Ideally I'd just use:
$thing.click(function(){...})
but the issue we're running into is that some of these blackberry devices have a very annoying delay from the time of the touch to it triggering a click.
The remedy is to instead use touchstart:
$thing.bind('touchstart', function(event){...})
But how do I go about binding both events, but only firing one? I still need the click event for keyboard devices, but of course, don't want the click event firing if I'm using a touch device.
A bonus question: Is there anyway to do this and additionally accommodate browsers that don't even have a touchstart event? In researching this, it looks like BlackBerry OS5 doesn't support touchstart so will also need to rely on click events for that browser.
ADDENDUM:
Perhaps a more comprehensive question is:
With jQuery, is it possible/recommended to handle both touch interactions and mouse interactions with the same bindings?
Ideally, the answer is yes. If not, I do have some options:
We use WURFL to get device info so could create our own matrix of devices. Depending on the device, we'll use touchstart OR click.
Detect for touch support in the browser via JS (I need to do some more research on that, but it seems like that is doable).
However, that still leaves one issue: what about devices that support BOTH. Some of the phones we support (namely the Nokias and BlackBerries) have both touch screens and keyboards. So that kind of takes me full circle back to the original question...is there a way to allow for both at once somehow?
Update: Check out the jQuery Pointer Events Polyfill project which allows you to bind to "pointer" events instead of choosing between mouse & touch.
Bind to both, but make a flag so the function only fires once per 100ms or so.
var flag = false;
$thing.bind('touchstart click', function(){
if (!flag) {
flag = true;
setTimeout(function(){ flag = false; }, 100);
// do something
}
return false
});
This is the fix that I "create" and it take out the GhostClick and implements the FastClick. Try on your own and let us know if it worked for you.
$(document).on('touchstart click', '.myBtn', function(event){
if(event.handled === false) return
event.stopPropagation();
event.preventDefault();
event.handled = true;
// Do your magic here
});
You could try something like this:
var clickEventType=((document.ontouchstart!==null)?'click':'touchstart');
$("#mylink").bind(clickEventType, myClickHandler);
Usually this works as well:
$('#buttonId').on('touchstart click', function(e){
e.stopPropagation(); e.preventDefault();
//your code here
});
Just adding return false; at the end of the on("click touchstart") event function can solve this problem.
$(this).on("click touchstart", function() {
// Do things
return false;
});
From the jQuery documentation on .on()
Returning false from an event handler will automatically call event.stopPropagation() and event.preventDefault(). A false value can also be passed for the handler as a shorthand for function(){ return false; }.
I had to do something similar. Here is a simplified version of what worked for me. If a touch event is detected, remove the click binding.
$thing.on('touchstart click', function(event){
if (event.type == "touchstart")
$(this).off('click');
//your code here
});
In my case the click event was bound to an <a> element so I had to remove the click binding and rebind a click event which prevented the default action for the <a> element.
$thing.on('touchstart click', function(event){
if (event.type == "touchstart")
$(this).off('click').on('click', function(e){ e.preventDefault(); });
//your code here
});
I succeeded by the following way.
Easy Peasy...
$(this).on('touchstart click', function(e){
e.preventDefault();
//do your stuff here
});
I believe the best practice is now to use:
$('#object').on('touchend mouseup', function () { });
touchend
The touchend event is fired when a touch point is removed from the touch surface.
The touchend event will not trigger any mouse events.
mouseup
The mouseup event is sent to an element when the mouse pointer is over the element, and the mouse button is released. Any HTML element can receive this event.
The mouseup event will not trigger any touch events.
EXAMPLE
$('#click').on('mouseup', function () { alert('Event detected'); });
$('#touch').on('touchend', function () { alert('Event detected'); });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1 id="click">Click me</h1>
<h1 id="touch">Touch me</h1>
EDIT (2017)
As of 2017, browsers starting with Chrome are making steps towards making the click event .on("click") more compatible for both mouse and touch by eliminating the delay generated by tap events on click requests.
This leads to the conclusion that reverting back to using just the click event would be the simplest solution moving forward.
I have not yet done any cross browser testing to see if this is practical.
check fast buttons and chost clicks from google https://developers.google.com/mobile/articles/fast_buttons
Well... All of these are super complicated.
If you have modernizr, it's a no-brainer.
ev = Modernizr.touch ? 'touchstart' : 'click';
$('#menu').on(ev, '[href="#open-menu"]', function(){
//winning
});
Generally you don't want to mix the default touch and non-touch (click) api. Once you move into the world of touch it easier to deal only with the touch related functions. Below is some pseudo code that would do what you want it to.
If you connect in the touchmove event and track the locations you can add more items in the doTouchLogic function to detect gestures and whatnot.
var touchStartTime;
var touchStartLocation;
var touchEndTime;
var touchEndLocation;
$thing.bind('touchstart'), function() {
var d = new Date();
touchStartTime = d.getTime();
touchStartLocation = mouse.location(x,y);
});
$thing.bind('touchend'), function() {
var d = new Date();
touchEndTime= d.getTime();
touchEndLocation= mouse.location(x,y);
doTouchLogic();
});
function doTouchLogic() {
var distance = touchEndLocation - touchStartLocation;
var duration = touchEndTime - touchStartTime;
if (duration <= 100ms && distance <= 10px) {
// Person tapped their finger (do click/tap stuff here)
}
if (duration > 100ms && distance <= 10px) {
// Person pressed their finger (not a quick tap)
}
if (duration <= 100ms && distance > 10px) {
// Person flicked their finger
}
if (duration > 100ms && distance > 10px) {
// Person dragged their finger
}
}
Another implementation for better maintenance. However, this technique will also do event.stopPropagation (). The click is not caught on any other element that clicked for 100ms.
var clickObject = {
flag: false,
isAlreadyClicked: function () {
var wasClicked = clickObject.flag;
clickObject.flag = true;
setTimeout(function () { clickObject.flag = false; }, 100);
return wasClicked;
}
};
$("#myButton").bind("click touchstart", function (event) {
if (!clickObject.isAlreadyClicked()) {
...
}
}
Just for documentation purposes, here's what I've done for the fastest/most responsive click on desktop/tap on mobile solution that I could think of:
I replaced jQuery's on function with a modified one that, whenever the browser supports touch events, replaced all my click events with touchstart.
$.fn.extend({ _on: (function(){ return $.fn.on; })() });
$.fn.extend({
on: (function(){
var isTouchSupported = 'ontouchstart' in window || window.DocumentTouch && document instanceof DocumentTouch;
return function( types, selector, data, fn, one ) {
if (typeof types == 'string' && isTouchSupported && !(types.match(/touch/gi))) types = types.replace(/click/gi, 'touchstart');
return this._on( types, selector, data, fn);
};
}()),
});
Usage than would be the exact same as before, like:
$('#my-button').on('click', function(){ /* ... */ });
But it would use touchstart when available, click when not. No delays of any kind needed :D
I just came up with the idea to memorize if ontouchstart was ever triggered. In this case we are on a device which supports it and want to ignore the onclick event. Since ontouchstart should always be triggered before onclick, I'm using this:
<script> touchAvailable = false; </script>
<button ontouchstart="touchAvailable=true; myFunction();" onclick="if(!touchAvailable) myFunction();">Button</button>
You could try like this:
var clickEvent = (('ontouchstart' in document.documentElement)?'touchstart':'click');
$("#mylink").on(clickEvent, myClickHandler);
In my case this worked perfectly:
jQuery(document).on('mouseup keydown touchend', function (event) {
var eventType = event.type;
if (eventType == 'touchend') {
jQuery(this).off('mouseup');
}
});
The main problem was when instead mouseup I tried with click, on touch devices triggered click and touchend at the same time, if i use the click off, some functionality didn't worked at all on mobile devices. The problem with click is that is a global event that fire the rest of the event including touchend.
This worked for me, mobile listens to both, so prevent the one, which is the touch event. desktop only listen to mouse.
$btnUp.bind('touchstart mousedown',function(e){
e.preventDefault();
if (e.type === 'touchstart') {
return;
}
var val = _step( _options.arrowStep );
_evt('Button', [val, true]);
});
This hasn't been mentioned here, but you may want to check out this link: https://joshtronic.com/2015/04/19/handling-click-and-touch-events-on-the-same-element/
To recap for posterity, instead of trying to assign to both handlers and then sort out the result, you can simply check if the device is a touchscreen or not and only assign to the relevant event. Observe:
var clickEvent = (function() {
if ('ontouchstart' in document.documentElement === true)
return 'touchstart';
else
return 'click';
})();
// and assign thusly:
el.addEventListener( clickEvent, function( e ){
// things and stuff
});
I am using this to bind my events so that I can test on touchscreens that handle both touchstart and click events which would fire twice, and on my development PC which only hears the click
One problem the author of that link mentions though, is touchscreen laptops designed to handle both events:
I learned about a third device I was not considering, the touchscreen laptop. It’s a hybrid device that supports both touch and click events. Binding one event means only that event be supported. Does that mean someone with a touchscreen and mouse would have to explicitly touch because that’s the only event I am handling?
Binding touchstart and click seemed ideal to handle these hybrid devices. To keep the event from firing twice, I added e.stopPropagation() and e.preventDefault() to the callback functions. e.stopPropagation() stops events from “bubbling up” to their parents but also keeps a second event from firing. I included e.preventDefault() as a “just in case” but seems like it could be omitted.
Being for me the best answer the one given by Mottie, I'm just trying to do his code more reusable, so this is my contribution:
bindBtn ("#loginbutton",loginAction);
function bindBtn(element,action){
var flag = false;
$(element).bind('touchstart click', function(e) {
e.preventDefault();
if (!flag) {
flag = true;
setTimeout(function() {
flag = false;
}, 100);
// do something
action();
}
return false;
});
I am also working on an Android/iPad web app, and it seems that if only using "touchmove" is enough to "move components" ( no need touchstart ).
By disabling touchstart, you can use .click(); from jQuery. It's actually working because it hasn't be overloaded by touchstart.
Finally, you can binb .live("touchstart", function(e) { e.stopPropagation(); }); to ask the touchstart event to stop propagating, living room to click() to get triggered.
It worked for me.
There are many things to consider when trying to solve this issue. Most solutions either break scrolling or don't handle ghost click events properly.
For a full solution see https://developers.google.com/mobile/articles/fast_buttons
NB: You cannot handle ghost click events on a per-element basis. A delayed click is fired by screen location, so if your touch events modify the page in some way, the click event will be sent to the new version of the page.
It may be effective to assign to the events 'touchstart mousedown' or 'touchend mouseup' to avoid undesired side-effects of using click.
Taking advantage of the fact that a click will always follow a touch event, here is what I did to get rid of the "ghost click" without having to use timeouts or global flags.
$('#buttonId').on('touchstart click', function(event){
if ($(this).data("already")) {
$(this).data("already", false);
return false;
} else if (event.type == "touchstart") {
$(this).data("already", true);
}
//your code here
});
Basically whenever an ontouchstart event fires on the element, a flag a set and then subsequently removed (and ignored), when the click comes.
Why not use the jQuery Event API?
http://learn.jquery.com/events/event-extensions/
I've used this simple event with success. It's clean, namespaceable and flexible enough to improve upon.
var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
var eventType = isMobile ? "touchstart" : "click";
jQuery.event.special.touchclick = {
bindType: eventType,
delegateType: eventType
};
If you are using jQuery the following worked pretty well for me:
var callback; // Initialize this to the function which needs to be called
$(target).on("click touchstart", selector, (function (func){
var timer = 0;
return function(e){
if ($.now() - timer < 500) return false;
timer = $.now();
func(e);
}
})(callback));
Other solutions are also good but I was binding multiple events in a loop and needed the self calling function to create an appropriate closure. Also, I did not want to disable the binding since I wanted it to be invoke-able on next click/touchstart.
Might help someone in similar situation!
For simple features, just recognize touch or click I use the following code:
var element = $("#element");
element.click(function(e)
{
if(e.target.ontouchstart !== undefined)
{
console.log( "touch" );
return;
}
console.log( "no touch" );
});
This will return "touch" if the touchstart event is defined and "no touch" if not. Like I said this is a simple approach for click/tap events just that.
I am trying this and so far it works (but I am only on Android/Phonegap so caveat emptor)
function filterEvent( ob, ev ) {
if (ev.type == "touchstart") {
ob.off('click').on('click', function(e){ e.preventDefault(); });
}
}
$('#keypad').on('touchstart click', '.number, .dot', function(event) {
filterEvent( $('#keypad'), event );
console.log( event.type ); // debugging only
... finish handling touch events...
}
I don't like the fact that I am re-binding handlers on every touch, but all things considered touches don't happen very often (in computer time!)
I have a TON of handlers like the one for '#keypad' so having a simple function that lets me deal with the problem without too much code is why I went this way.
Try to use Virtual Mouse (vmouse) Bindings from jQuery Mobile.
It's virtual event especially for your case:
$thing.on('vclick', function(event){ ... });
http://api.jquerymobile.com/vclick/
Browser support list: http://jquerymobile.com/browser-support/1.4/
EDIT: My former answer (based on answers in this thread) was not the way to go for me. I wanted a sub-menu to expand on mouse enter or touch click and to collapse on mouse leave or another touch click. Since mouse events normally are being fired after touch events, it was kind of tricky to write event listeners that support both touchscreen and mouse input at the same time.
jQuery plugin: Touch Or Mouse
I ended up writing a jQuery plugin called "Touch Or Mouse" (897 bytes minified) that can detect whether an event was invoked by a touchscreen or mouse (without testing for touch support!). This enables the support of both touchscreen and mouse at the same time and completely separate their events.
This way the OP can use touchstart or touchend for quickly responding to touch clicks and click for clicks invoked only by a mouse.
Demonstration
First one has to make ie. the body element track touch events:
$(document.body).touchOrMouse('init');
Mouse events our bound to elements in the default way and by calling $body.touchOrMouse('get', e) we can find out whether the event was invoked by a touchscreen or mouse.
$('.link').click(function(e) {
var touchOrMouse = $(document.body).touchOrMouse('get', e);
if (touchOrMouse === 'touch') {
// Handle touch click.
}
else if (touchOrMouse === 'mouse') {
// Handle mouse click.
}
}
See the plugin at work at http://jsfiddle.net/lmeurs/uo4069nh.
Explanation
This plugin needs to be called on ie. the body element to track touchstart and touchend events, this way the touchend event does not have to be fired on the trigger element (ie. a link or button). Between these two touch events this plugin considers any mouse event to be invoked by touch.
Mouse events are fired only after touchend, when a mouse event is being fired within the ghostEventDelay (option, 1000ms by default) after touchend, this plugin considers the mouse event to be invoked by touch.
When clicking on an element using a touchscreen, the element gains the :active state. The mouseleave event is only fired after the element loses this state by ie. clicking on another element. Since this could be seconds (or minutes!) after the mouseenter event has been fired, this plugin keeps track of an element's last mouseenter event: if the last mouseenter event was invoked by touch, the following mouseleave event is also considered to be invoked by touch.
Here's a simple way to do it:
// A very simple fast click implementation
$thing.on('click touchstart', function(e) {
if (!$(document).data('trigger')) $(document).data('trigger', e.type);
if (e.type===$(document).data('trigger')) {
// Do your stuff here
}
});
You basically save the first event type that is triggered to the 'trigger' property in jQuery's data object that is attached to the root document, and only execute when the event type is equal to the value in 'trigger'. On touch devices, the event chain would likely be 'touchstart' followed by 'click'; however, the 'click' handler won't be executed because "click" doesn't match the initial event type saved in 'trigger' ("touchstart").
The assumption, and I do believe it's a safe one, is that your smartphone won't spontaneously change from a touch device to a mouse device or else the tap won't ever register because the 'trigger' event type is only saved once per page load and "click" would never match "touchstart".
Here's a codepen you can play around with (try tapping on the button on a touch device -- there should be no click delay): http://codepen.io/thdoan/pen/xVVrOZ
I also implemented this as a simple jQuery plugin that also supports jQuery's descendants filtering by passing a selector string:
// A very simple fast click plugin
// Syntax: .fastClick([selector,] handler)
$.fn.fastClick = function(arg1, arg2) {
var selector, handler;
switch (typeof arg1) {
case 'function':
selector = null;
handler = arg1;
break;
case 'string':
selector = arg1;
if (typeof arg2==='function') handler = arg2;
else return;
break;
default:
return;
}
this.on('click touchstart', selector, function(e) {
if (!$(document).data('trigger')) $(document).data('trigger', e.type);
if (e.type===$(document).data('trigger')) handler.apply(this, arguments);
});
};
Codepen: http://codepen.io/thdoan/pen/GZrBdo/
I am trying to bind the keyboard event ctrl+z through jquery only when a backbone view is rendered and when view is removed, I am unbinding it. But the problem is I want to unbind only the ctrl+z event only for current view.
on Initializing view:
$(document).keydown(function(e){
if((e.which === 90 && e.ctrlKey && e.shiftKey) || (e.which === 89 && e.ctrlKey)){
console.log('control + shift + z || control + y');
//redoAction;
}
else if(e.which === 90 && e.ctrlKey){
console.log('control + z');
//undoAction;
}
});
On removing view:
$(document).off('keydown');
But the last statement will unbind all the keyboard listeners. I don't want that.
Give a name to your handler function in a declaration (or by assigning it to a variable). Then you can use that name to refer the handler within .off():
// Create a named function
function handler (e) {
if((e.which === 90 ...)
}
// Attach event
$(document).on('keydown', handler);
//Detach event
$(document).off('keydown', handler);
Try using "on" handler with a selector and then you can use same selector to unwire events with 'off'. Following is a basic example though not exactly in your problem space, but the concept should be useful to you too.
<p id="pOne">Click this paragraph to change its background color.</p>
<button id="btn1">Remove the click event with off()</button>
<script>
$(document).ready(function(){
$(document).on("click","#pOne",function(){ //Notice use of "on" here
$(this).css("background-color", "pink");
});
$("#btn1").click(function(){
$(document).off('click','#pOne');
//Notice here same selector: #pOne, used with "on"
});
});
</script>
To check the specific event, you can use handler function as last argument similar to other jQuery events.
More from jQuery 'Off' documentation
Hope this may help you.
You can take advantage of jQuery's event namespacing feature:
// Add multiple listeners under the 'foo' namespace.
$(document).on('keydown.foo', handler1);
$(document).on('keyup.foo', handler2);
// Unbind all listeners at once without keeping reference to the various handlers.
$(document).off('.foo');
This is a actually a pattern used by Backbone's View class to easily remove all handlers that are added by the framework to the view's DOM element.
I have two event handlers, one for keydown and one for keyup. The keydown event handler triggers an alert message, but this prevents the keyup event from firing.
You can see a very simple example here: http://jsfiddle.net/boblauer/jaGwT/ When the keydown opens an alert, the keyup is not fired, but when an alert is not opened, the keyup is fired. Here's the code from the jsfiddle:
var i = 0;
window.addEventListener('keydown', function(e) {
if (i++ % 2) alert('down');
console.log('down');
});
window.addEventListener('keyup', function(e) {
alert('up');
console.log('up');
});
I have a library that supports listening to multiple key combinations (such as 'd + f'), so when a key is pressed, I need to add it to a list of keys that are currently pressed, and when a key is released, I need to remove it from said list. The problem I'm running to is, if I want an alert to show when d + f are pressed at the same time, my code to remove those keys from the 'currently pressed' list never fires, because my keyup handler is never called.
I can't think of a good work around to this problem. Any ideas?
The alert prevents the event from happening. What you could do instead is trigger this function manually, because it happens anyways.
var keyupfunction = function(e){
alert('up');
console.log('up');
}
window.addEventListener('keyup', keyupfunction);
window.addEventListener('keydown', function(e) {
if (i++ % 2) alert('down');
console.log('down');
keyupfunction(e);
});
But really, you shouldn't be using alerts. It prevents these events, but who knows what else it might break. Use something custom instead.
Whats the easiest way to temporarily disable all mouse click/drag etc events through javascript?
I thought I could do document.onclick = function() { return false; }; ...etc, but that's not working.
If the objective is to disable click on the whole page then you can do something like this
document.addEventListener("click", handler, true);
function handler(e) {
e.stopPropagation();
e.preventDefault();
}
true argument in addEventListener would ensure that the handler is executed on the event capturing phase i.e a click on any element would first be captured on the document and the listener for document's click event would be executed first before listener for any other element. The trick here is to stop the event from further propagation to the elements below thus ending the dispatch process to make sure that the event doesn't reach the target.
Also you need to stop default behavior associated with event target elements explicitly as they would be executed by default after the dispatch process has finished even if the event was stopped propagating further from above
It can be further modified to use selectively.
function handler(e) {
if(e.target.className=="class_name"){
e.stopPropagation();
e.preventDefault();
}
}
handler modified this way would disable clicks only on elements with class "class_name".
function handler(e) {
if(e.target.className!=="class_name") {
e.stopPropagation()
}
}
this would enable clicks only on elements with class "class_name".
Hope this helped :)
Dynamically disable all clicks on page
let freezeClic = false; // just modify that variable to disable all clics events
document.addEventListener("click", e => {
if (freezeClic) {
e.stopPropagation();
e.preventDefault();
}
}, true);
I often use it while loading or to avoid user to accidentally clic twice on an action button. Simple and performance friendly :)
Please check this working example
Alternative CSS way
Another one that I really like because of the visual feedback the user have:
/* style.css */
.loading {
cursor: wait; /* busy cursor feedback */
}
.loading * {
/* disable all mouse events on children elements */
pointer-events: none;
}
A simple example to dynamically add the .loading class:
const elm = document.getElementById('myElm')
elm.classList.add('loading')
myAsyncFunction().then(() => elm.classList.remove('loading'))
If you want absolutely nothing draggable/clickable, disabling typing in input fields etc, I'd consider showing a absolutely positioned transparent div over the entire page, so that every click will be on the div, which will do nothing. That will grant you swift and neat switching on and off of this click-disabler, without having to register heaps of listeners
The winning answer works well, but if you had pass the capture true boolean value, at the moment you want to remove the listener, you have to pass the exact same value. Otherwise, the listener removal will not work.
Example:
listener addition
document.addEventListener('click', DisableClickOnPage.handler, true);
listener removal
document.removeEventListener('click', DisableClickOnPage.handler, true);
Doc: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
window.addEventListener("click", (e) => {
e.stopPropagation();
e.stopImmediatePropagation();
e.preventDefault();
}, true)
If we added a listener to document instead of window anyone can add a listener to window and it works. Because of document child of window and its events trigger always after window events.
We use 3 method of Event object.
stopPropagation for prevent all capturing and bubbling
stopImmediatePropagation for prevent same listeners (e.g. another window click listeners)
preventDefault for prevent all user agent event (e.g anchor href or form submit)
If onclick = null has been executed how to revoke the onclick event to normal functioning.. or
Link text
<script type="text/javascript">
function yourFunction(anchor)
{ if(anchor.disabled) return;
/* Your function here */
}
</script>
This article would probably be useful:
http://www.computerhowtoguy.com/how-to-use-the-jquery-unbind-method-on-all-child-elements/
One part in particular is a recursive function that removes all click events. Remember that jQuery will remove click events IF the click event was created using jQuery. the function given in the article will remove both those created with jQuery and those that were not. The function given is this:
function RecursiveUnbind($jElement) {
// remove this element's and all of its children's click events
$jElement.unbind();
$jElement.removeAttr('onclick');
$jElement.children().each(function () {
RecursiveUnbind($(this));
});
}
You would call the function like this:
RecursiveUnbind($('#container'));
That function takes a jQuery object parameter, but you could easily change it up to pass a string as the name of the ID for the element, or however you think is best.
To prevent the default behavior of an event, use event.stopPropagation() and event.preventDefault() in your event handler. And don't forget, return false; is another method for indicating that you want to cancel the default action...
The Event property returnValue indicates whether the default action for this event has been prevented or not. It is set to true by default, allowing the default action to occur. Setting this property to false prevents the default action. (Source: MDN Web Docs: Event.returnValue.)
Typically, we return a value from any function when it has any meaningful or useful purpose -- return false to cancel an event is meaningful because it indicates a failed event, and it's useful because the event-handler uses it.
For greatest cross-browser compatibility, remember to return false;...
document.addEventListener("click",handler,true);
function handler(e){
e.stopPropagation();
e.preventDefault();
return false;
}