Disable the firing of touchcancel in Android Browser - javascript

I am making a mobile website that uses JavaScript touch events. Things work fine in iOS Safari and Chrome for Android, but the stock Android Browser (version 4.1.2) is giving me trouble.
During a touch process, the touchstart and touchmove events are called as expected. However, one of the actions performed by the touchmove handler seems to trigger a premature touchcancel event. (I'm not sure whether this is significant, but the action that triggers the touchcancel is the modification of an SVG object's viewBox attribute.) If I comment out this action, the touch process proceeds normally (i.e., completion of touchmove through to touchend).
All of my touch handlers call the preventDefault() function, so the issue isn't the one that's described in this bug: https://code.google.com/p/android/issues/detail?id=19827.
I've read that there is a lot of inconsistency among browsers as to when touchcancel is called. The stock Android browser is the only one that is problematic for me.
Is there a workaround out there? For example, is there away I can completely disable the touchcancel event? Another idea I had was to have the touchcancel handler programmatically trigger another touchstart/touchmove event, but I didn't get very far with that. Any ideas would be appreciated.

I know it's a bit late but if someonee else is facing this issue, here's a small jQuery extension that explains how to deal with pointercancel and touchcancel events.
Basically, if you switch to pointer events you will be able to prevent pointercancel by simply using the touch-action CSS property with a value of none, as long as the browser has both features correctly implemented. (Android 5+, Chrome 55+, IE11, Edge, etc)
If you really must use the legacy touch events instead, you'll have to implement event.preventDefault() in your touchmove events and this will prevent touchcancel from firing, but it will also disable entirely the browser handling of any default action like a pan or click.
As a final note, I wouldn't use touch events + touch-action CSS rules because touch-action was only recently added, at the same that that Pointer Events. So while that combination may work in newer browsers, it will most certainly fail in older ones (by triggering touchcancel event unexpectedly).
Check the README from the jQuery extension I posted because it explains the implications of using either TouchEvent and PointerEvent interfaces.

If you are useing hammer.js, you can disable pointer-events (which cause problems like this), by adding:
delete window.PointerEvent;
in your index.html PRIOR to loading hammer.js, and it will ignore those events.
You can also set SUPPORT_POINTER_EVENTS = false; (line 384 of v2.0.8) to do the same thing.
Ideally the devs would add the ability to turn this off, but so goes the open source dilemma...

I think touchcansel is special event that triggers when user touched button in specific behavior.
Ex. User touch button, but didn't removed his finger from display on exactly that button, instead of this he could move it a bit to the left, and end his action there. In that case touchcancel would appear.
So if you don't want that action to fire, i think you have to remove all handlers that connected with that action in android browser.
$('selector').off('touchcancel'), of if it's delefated $('parent').undelegate('touchcancel','selector')
I don't think that there is problem with e.preventDefault(). Only may be if you click on a link, and page starts to reload. May be page is reloaded when you click on button? and you wait for some actions after page reloaded, you actually may not understand, that it happend.

Related

How to prevent default handling of touch events?

I am trying to do something similar to what embedded Google maps do. My component should ignore single touch (allowing user to scroll page) and pinch outside of itself (allowing user to zoom page), but should react to double touch (allowing user to navigate inside the component) and disallow any default action in this case.
How do I prevent default handling of touch events, but only in the case when user is interacting with my component with two fingers?
What I have tried:
I tried capturing onTouchStart, onTouchMove and onTouchEnd. It turns out that on FF Android the first event that fires when doing pinch on component is onTouchStart with a single touch, then onTouchStart with two touches, then onTouchMove. But calling event.preventDefault() or event.stopPropagation() in onTouchMove handler doesn't (always) stop page zoom/scroll. Preventing event escalation in the first call to onTouchStart does help - unfortunately at that time I don't know yet if it's going to be multitouch or not, so I can't use this.
Second approach was setting touch-action: none on document.body. This works with Chrome Android, but I could only make it work with Firefox Android if I set this on all elements (except for my component). So while this is doable, it seems like it could have unwanted side effects and performance issues. EDIT: Further testing revealed that this works for Chrome only if the CSS is set before the touch has started. In other words, if I inject CSS styles when I detect 2 fingers then touch-action is ignored. So this is not useful on Chrome.
I have also tried adding a new event listener on component mount:
document.body.addEventListener("touchmove", ev => {
ev.preventDefault();
ev.stopImmediatePropagation();
}, true);
(and the same for touchstart). Doing so works in Firefox Android, but does nothing on Chrome Android.
I am running out of ideas. Is there a reliable cross-browser way to achieve what Google apparently did, or did they use multiple hacks and lots of testing on every browser to make it work? I would appreciate if someone pointed out an error in my approach(es) or propose a new way.
TL;DR: I was missing { passive: false } when registering event handlers.
The issue I had with preventDefault() with Chrome was due to their scrolling "intervention" (read: breaking the web IE-style). In short, because the handlers that don't call preventDefault() can be handled faster, a new option was added to addEventListener named passive. If set to true then event handler promises not to call preventDefault (if it does, the call will be ignored). Chrome however decided to go a step further and make {passive: true} default (since version 56).
Solution is calling the event listener with passive explicitly set to false:
window.addEventListener('touchmove', ev => {
if (weShouldStopDefaultScrollAndZoom) {
ev.preventDefault();
ev.stopImmediatePropagation();
};
}, { passive: false });
Note that this negatively impacts performance.
As a side note, it seems I misunderstood touch-action CSS, however I still can't use it because it needs to be set before touch sequence starts. But if this is not the case, it is probably more performant and seems to be supported on all applicable platforms (Safari on Mac does not support it, but on iOS it does). This post says it best:
For your case you probably want to mark your text area (or whatever)
'touch-action: none' to disable scrolling/zooming without disabling
all the other behaviors.
The CSS property should be set on the component and not on document as I did it:
<div style="touch-action: none;">
... my component ...
</div>
In my case I will still need to use passive event handlers, but it's good to know the options... Hope it helps someone.
Try using an if statement to see if there is more than one touch:
document.body.addEventListener("touchmove", ev => {
if (ev.touches.length > 1) {
ev.preventDefault();
ev.stopImmediatePropagation();
}
}, true);
This is my idea:
I used one div with opacity: 0 to cover the map with z-index > map z-index
And I will detect in the covered div. If I detect 2 fingers touched in this covered div, I will display: none this div to allow user can use 2 finger in this map.
Otherwise, in document touchEnd I will recover this covered div using display: block to make sure we can scroll.
In my case I have solved it with
#HostListener('touchmove', ['$event'])
public onTouch(event: any): void {
event.stopPropagation();
console.log('onTouch', event);
}

Why is touchstart event after click?

Here's something odd, that I felt sure was working in earlier mobile browsers: In Chrome on Android, and Safari on iOS, it seems the touchstart event is fired after the click event, not before. When did this change?
A simple example:
jQuery(function($) {
var touched = false;
$('#clicky').on('touchstart', function(evt){
touched = true;
evt.preventDefault();
})
.click(function(){
if (!touched) {
alert("somehow touch didn't fire")
}
});
})
Run this fiddle, and you'll see the alert can pop up on Android and iOS, when it should actually never show!
http://jsfiddle.net/quxnxu7d/2/
I ran it through Chrome on Android and it worked as you expected for me. I added an alert to the touchstart handler and it fired to be sure that it was firing first and it did.
Take a look at the touch events mdn article. The article specifically mentions:
calling preventDefault() on a touchstart or the first touchmove event of a series prevents the corresponding mouse events from firing
Click is a mouse event so it "should" work as you expect (and it was working for me). I'd verify that the events are indeed running out of order (use console.log() instead of alert()) on your target browsers. If they are, which is perfectly possible with less than perfect browsers/specs, try using a different mouse event like mouseup. My guess is that you'll be able to find an event that works consistently.
Good luck!
Have you tried using mousedown instead of click? That way you should get different events for touch and click without any double firing. You will also likely need to use keydown to keep this site accessible.
There is a 300ms delay between a physical tap and the firing of a click event on some mobile browsers (e.g. iOS Safari)
I ran into the same issue and FastClick jQuery plugin fixed it for me
Have a look FastClick

IE11 shunting pointer events into touch events?

I've created a web application that works well with touch events and click events with their own respective handlers.
However, issues are encountered when moving to touch IE11. Touch works fine on other platforms. The mouse on IE11 works great. But if you use both a touch screen and IE11, then the touch events are wrapped as pointer events instead of touch events or click events.
Is there any way to just globally map pointer touch events to pre-defined touch events for the entire window/document? It seems that it is already doing this by default for click events in IE11...
I tried following code example as displayed here: JavaScript mapping touch events to mouse events
And ended up with:
if(window.PointerEvent)
{
...
document.onpointerdown = function(e){
var event = document.createEvent("TouchEvent");
...
}
...
}
However, IE errors at document.crateEvent("TouchEvent") claiming that it is "not supported." However, the TouchEvent object is documented in IE as being supported: https://msdn.microsoft.com/en-us/subscriptions/dn792856(v=vs.85)
as specified in http://www.w3.org/TR/touch-events/#list-of-touchevent-types
I've also tried changing it to
var event = document.createEvent("UIEvent");
which IE allows, but then does later not recognize
event.initTouchEvent(someParameters);
I'm starting to believe that IE11 doesn't support creation of touch events.
I've also seen talk about changing the .css files to include combinations of:
touch-action: none
ms-touch-action: none
pointer-events: none
but completely disabling touch tends to break the application in other ways that I was hoping to avoid.
Is there any way to fire touch events instead of Microsoft's pointer events?
I would say you're actually going about this the wrong way. There are several pollyfill libraries to map touch events to pointer events. HandJS and jQuery PEP are two well functioning examples. Given that the only modern browser that doesn't support pointer events at this time is Safari, that seems the better way to go (Who wants pointer events...).

Android browsers not handling touchmove events correctly

When I try to inspect the touchmove event in this jsbin demo it only triggers once in Chrome and Opera for Android, and immediately after that, it triggers a touchcancel event, instead of continuing to trigger touchmove events?
Based on both the W3C specs, and the behaviour of the touchmove event in both Firefox for Android and Android's default browser, it seems to me that the way the touch events are supposed to work is that the touchmove event keeps triggering while the touch is still on the page. After trying to test in this jsbin though, I got the following log messages:
touchstart event; starting on (140,197) on the screen, or (381,536) on the page.
touchend event; starting on (undefined,undefined) on the screen, or (undefined,undefined) on the page.
touchstart event; starting on (181,137) on the screen, or (492,372) on the page.
touchmove event; starting on (182,153) on the screen, or (495,416) on the page.
touchcancel event; starting on (undefined,undefined) on the screen, or (undefined,undefined) on the page.
This is what happened when I first tapped the screen (shown via a touchstart and touchend), and then dragged the screen (touchstart, touchmove and touchcancel). Going by the same specs mentioned above, the touchcancel event should only run when something interferes , such as the browser interface (if I understand that correctly).
Since I was simply sliding my finger over the body, without leaving the window at all, I am really puzzled by this, so would anybody know why this is happening?
I am getting this unexpected result in Chrome 32 and Opera 19 for Android.
Turns out the problem here was simply the fact the event handler didn't have an event.preventDefault() in it, so the original action still executed, which apparently interrupted the touch event. To fix this, simply add e.preventDefault() in the current event handler function to cancel the current event, and make it work as expected in Chrome and Opera too.
Working demo.

Javascript Drag and drop in windows phone 7.5

I'm trying to get drag and drop functionality to work in a windows 7.5 phone.
Initially i tried using the jquery ui draggable which worked in all browsers except windows 7.5.
Later on exploring further, i found out that there are no touchstart, touchend and touchmove events in windows.
So i tried getting it to work using mousedown, mousemove events which shockingly were firing. But they donot fire in a proper sequence. (mousemove fires before mousedown).
Nor did the dragstart and dragend events work.
I need to get this to work. Do you have any suggestions ?
/*Eg1:does not work*/
$("#draggable").draggable();
/*Eg2:This event does not fire*/
$("#draggable").bind("touchstart touchmove touchend", function(){
alert("touch events supported");})
/*Eg3: In this case, mousemove fires before mousedown.*/
$("#draggable").bind("mousedown", function(){
alert("mousedown");});
$("#draggable").bind("mousemove", function(){
alert("mousemove");});
I only have experience working with Javascript in Windows Phone 8 which uses IE 10 (as opposed to IE 9 in WP7.5 and WP7.8) but you could give my approach a try.
In WP8 (IE10) Microsoft supports the pointer events MSPointerUp MSPointerMove MSPointerDown instead of touchstart, touchmove and touchend events. Try using these to see if they work.
In the example I had created, however, I did not even need to use those proprietary events as IE 10 happily used the standard mouse events (mouseup mousemove mousedown) and worked well to an extent. I did not have any problem with the order of events (like you stated in the question). The main problem that arose for me while dragging was that the mousemove event stopped being triggered (by the browser) and the browser instead started panning the page leaving my dragged elements in limbo state (no mousemove or mouseup triggered). Using event.preventDefault() did not help either as it did not prevent the page from panning. To overcome this problem I went through Microsoft's documentation about their touch events and came across this CSS line.
-ms-touch-action: none;
I applied this on my elements to be dragged and my problem was solved. You can read all about the above proprietary CSS property here. Be careful not to apply this property to the entire body of the page (I had done that earlier) as it prevents the default browser behaviour of page panning and zooming. As I said apply it only on the elements to be dragged and it works.
Yet again this is my experience for WP 8. I do not know if it works for previous versions of Windows Phone.

Categories