I have an issue concerning the keyboards events in JS.
First of all, please do not answer me to use jQuery methods, I know most of it (bind/unbind, on/off, one...) but I work with an internal framework that have to work without jQuery, even if jQuery is used on most of our projects.
So, I have a module, in fact a swipe tool based on Swipe.js and extended to work on web and mobile environments (compatibility needed for IE 7+, WebKit (Chrome & Safari), Moz, Opera and IE10 / Windows Phone)
In do not have any problem with mouse/touch events, the binding and unbinding methods inspired from the mobile HTML5 BP seems to work very well with a small correction for the detachEvents method.
And then I would to implement a keyboard control feature based on 'keydown' events.
(BTW, I am not sure to make a good difference between keydon and keypress events, except the keypressEvent.preventDefault() do not prevent the small scroll effect, so I use keydown.)
As it is possible to add many Swipes on the same page, I start to listen the keydown events only when any Swipe is focused (Note that I add a "tabindex" attribute to allow the element to get focused).
<div id="swipe1" class="swipe" tabindex='0'>
<ul>
[...]
</ul>
</div>
Then when the Swipe handle a 'touchstart' / 'click' / ' MSPointerDown' event,
I focus it :
onTouchStart: function(e) {
this.container.focus(); // Refers to the div#swipe1.swipe element
[...]
return false;
}
onFocus: function (e) {
if (this.activateKeyboardControls) { // Keyboard control is optional
this.addListener(document, 'keydown', this, true);
}
}
onBlur: function (e) {
if (this.activateKeyboardControls) { // Keyboard control is optional
this.removeListener(document, 'keydown', this, true);
}
}
But unfortunately, the removeListener does not work.
I mean, when the element loses the focus (blur event fired), it still handle the keydown events...
Is it because it is binded on the document object ?
I have read some solutions working with some booleans but I am looking for a cleaner way the manage it.
This is annoying, because when I give the focus to many Swipes, each of them is swiping when I press the keyboard.
I'm not sure of the reason why my answer has been deleted - more than 2 weeks after posting - but anyway, this is how I solved it :
It comes from the 'type' parameter of the addEventListener / attachEvent method, the first of one so...
I bind it on the object instead of on the window, and without bubbling.
var target = e.target || e.srcElement;
if (this.activateKeyboardControls) {
this.addListener(target, 'keydown', this, false);
}
Related
I've used onclick events in my website. But when I open it in google chromes' developer mode's mobile view, nothing happens on touch on the elements which work on click with mouse. So my question is:
Do I have to also add ontouch events along with onclick events, or onClick event work on touch on all touch-screen devices?
P.S: You can see all of my codes here: https://github.com/SycoScientistRecords/sycoscientistrecords.github.io/
Or at the live website: http://sycoscientistrecords.github.io
And no I haven't tested the website on real phone.
onclick works fine on touchscreens; I've used it several times and have never had any problem.
You could consider using onmousedown instead of onclick. Or use jQuery to detect taps.
I found this detailed writeup at MDN very helpful. In particular:
the browser may fire both touch events and mouse events in response to the same user input [emphasis mine]
and
the element's touch event handlers should call preventDefault() and no additional mouse events will be dispatched
So, your touchstart or touchend listener can call evt.preventDefault() and your mousedown / mouseup listeners won't fire because they come later in the chain.
In Angular, I was able to detect whether I'd clicked a button using my mouse or my laptop's touchscreen, by changing (click)="doSomething()" to (mouseup)="doSomething(false)" (touchend)="doSomething(true); $event.preventDefault()". The method is called with true for touch events and false for mouse events.
onclick may not work on touch devices, I had this issue and the event ontouchstart sorts it.
if you use ontouchstart and onclick watch that you don't trigger the event twice.
this is another post related
onClick not working on mobile (touch)
New browsers have a pointerType which determines if the onClick is made by a mouse or via a touch. If you just want make adjustments in user behavior based on the input, using pointerType is the safest way.
if you are using jQuery:
$(selector).click(e => {
if (e.pointerType === "mouse") {} // mouse event
else {} // touch event
});
if you are using vanilla JS:
element.addEventListener('click', e => {
if (e.pointerType === "mouse") {} // mouse event
else {} // touch event
});
If you are using React, the event is wrapped around a synthetic event. To access the pointerType, you have to use the nativeEvent of the react event. Here is what you need to consider (especially if you are using Typescript). If the event is triggered by a mouse, the native event is an instance of MouseEvent which does not have pointerType, so, first you need to check the type of native event which will also take care of the typing problems in TS
<div
onClick={e => {
if (e.nativeEvent instanceof PointerEvent && e.nativeEvent.pointerType === 'touch') {} // Touch Event
else {} // Mouse Event
}}
></div>
Pro tip: If you want to test the touch event in development, use Chrome following this. Note that Safari has a responsive mode which simulates the framework of iPhones and iPads. However, Safari always registers a mouse event even when you are in responsive design mode and have selected an iPhone or iPad.
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>
var ClickOrTouchEvent = ("ontouchend" in document.documentElement ? "touchend" : "click");
$("#MyButton").on(ClickOrTouchEvent, function () {
// Do something
});
This works, but there is probably a more elegant way to detect touch events support and assign either a "click" or "touchend" event.
There are a number of plugins that people have created to add more advanced touch support to their jquery based web apps like drag and drop. You can use one of those instead of trying to make your own.
That said they all seem to have their weird issues in my experience so you will probably have to tweek. Looks like your solution is in the same family as most of theirs. Here are a couple popular plugins and the solutions they employ for deciding touch or click:
TouchPunch adds touch to everything like so:
// Detect touch support and save it in support
$.support.touch = 'ontouchend' in document;
// Ignore browsers without touch support
if (!$.support.touch) {
return;
}
//rest of plugin code follows
jquery-ui-for-ipad-and-iphone has a method you have to call to add touch events to the desired element... but only if the browser supports it.
// Same deal. Detect touch support and save it in support
$.extend($.support, {
touch: "ontouchend" in document
});
//
// Hook up touch events
//
$.fn.addTouch = function() {
if ($.support.touch) {
this.each(function(i,el){
el.addEventListener("touchstart", iPadTouchHandler, false);
el.addEventListener("touchmove", iPadTouchHandler, false);
el.addEventListener("touchend", iPadTouchHandler, false);
el.addEventListener("touchcancel", iPadTouchHandler, false);
});
}
};
....
Then call addTouch on the elements with jQuery UI functions applied:
$('Element').dialog().addTouch();
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
I am trying to implement a scrollable element for a mobile app and it looks like you must use preventDefault on the initial touchStart event, otherwise the browser will not fire all the touchMove events (presumably for performance reasons).
So it would seem that if I want to allow touch scrolling on an overflown element, the user will not be able to scroll the page as per usual when touching that element. This is problematic if the overflown element takes up a large portion of the viewport.
Is their a workaround for this?
Take a look at this library
http://api.mutado.com/mobile/mtdtouch/js/
The "core" javascript includes a base UIComponent optimized for touch events (webkit).
The UI.Scroll component in example manage the "prevent default issue" for you.
Try to subclass the UIComponent and implement your own events handler like this
$MTD.YourOwnComponent = $.klass( $MTD.UIComponent, {
touchesBegan: function( e ) {
// your stuff
},
touchesMoved: function( e ) {
// your stuff
},
touchesEnded: function( e ) {
// your stuff
}
});
Hope this helps.
Here's a simple workaround: drop the touchstart handler. You can reconstruct most of what's going on with just the touchmove, touchend, and touchleaving handlers.
In the browser I tested with (Chrome), scrolling happens as long as you don't have a touchstart handler; it doesn't care about the other ones. As long as you aren't actively calling ev.preventDefault in the touchmove handlers, scrolling works.
Assuming what you want to do will work fine despite only finding out about a touch when the finger starts moving, instead of when the finger initially lands, this workaround should work acceptably.
... And also I'm assuming other browsers use the same logic as Chrome.