I'm trying to use JavaScript and jQuery to capture touch events. But I'm seeing some very odd behavior in the Web browser on Android 2.3.2: whenever I tap the screen, and then quickly tap somewhere else on the screen, the browser:
- momentarily shows an orange border and highlight over the entire screen, and
- sends me the wrong events.
The orange border seems to be just a related symptom of the same underlying problem, so I'm not too worried about it -- it's actually convenient for being able to tell when the browser is screwing things up. What I really want to know is, how can I consistently get the right touch events for two quick taps? I believe that when that problem is solved, the orange border will go away as well.
What follows are all the painful details I've worked out so far.
Here's a page that shows the problem and displays lots of diagnostic information about the details and timing of each event that's received. You're sure to get the orange flash / bad events if you tap inside the blue rectangle, then quickly tap inside the black rectangle.
My jQuery code is pretty standard. The log
function's implementation isn't important; the problem is that the browser doesn't call it when it should.
el = $('#battle');
el.on('touchstart', function(event) {
log(event);
return event.preventDefault();
});
el.on('touchend', function(event) {
return log(event);
});
el.on('touchcancel', function(event) {
return log(event);
});
el.mousedown(function(event) {
log(event);
return event.preventDefault();
});
return el.mouseup(function(event) {
return log(event);
});
More details on the phenomena I described initially:
Orange border and highlight: This is the same orange border and highlight that the browser draws around a hyperlink when you click it. But there are no hyperlinks on the page, and the browser draws this orange border around the whole screen -- or more specifically, around the outer <div id="battle">
that I'm hooking events on via jQuery.
Wrong events: In my touchstart
event handler, I'm calling event.preventDefault()
, to tell the browser not to scroll, not to synthesize mouse events, etc. Therefore, I expect to get only touchstart
and touchend
events. And I do, for the first tap. But instead of touchstart
/touchend
for the second tap, I get all number of combinations of touch events, synthesized mouse events, and the occasional touchcancel
for the second tap, or even repeated events for the first tap. Details below.
This behavior also only occurs in very particular circumstances:
- The first tap must be short (less than ~200ms).
- The second tap must come quickly thereafter (less than ~450ms after the first tap's
touchstart
). - The second tap must be at least 150 pixels away from the first tap (measured along the diagonal from the coordinates of the first tap's
touchstart
). - If I remove my code that hooks
mousedown
andmouseup
, the orange rectangles no longer appear. However, the touch events are sometimes still garbled.
As far as what I mean by the events being garbled, here's what I see. When I write "1:", that means the events are for the first tap's coordinates; "2:" means the second tap's coordinates. I saw the following patterns of events (percentages indicate how many times each one came up after 100 trials):
- (50%) 1:touchstart 1:touchend 1:mousedown 1:mouseup (short delay) 2:mousedown 2:mouseup
- (35%) 1:touchstart 1:touchend 2:touchstart 1:mousedown 1:mouseup 2:touchend
- (10%) 1:touchstart 1:touchend 2:touchstart 1:mousedown 1:mouseup 2:touchcancel (short delay) 2:mousedown 2:mouseup
- (3%) 1:touchstart 1:touchend 2:touchstart 2:touchend (short delay) 1:mousedown 1:mouseup
- (2%) 1:touchstart 1:touchend 1:mousedown 1:mouseup (and nothing at all for the second tap)
Some combinations of events seem to come up more often depending on how quickly I tap, but I haven't quite nailed down the pattern. (Two quick, crisp taps seem more likely to come in under the second item above, whereas a more rapid-fire approach with less emphasis on crispness seems more likely to be the first item. But I haven't identified specific timing numbers that lead to each.) Similarly, the "short delays" indicated above can be anywhere from ~150ms to ~400ms; I haven't reverse-engineered the whole pattern there either.
If I don't hook mousedown
and mouseup
, the distribution is roughly this:
- (40%) 1:touchstart 1:touchend 2:touchstart 2:touchcancel
- (35%) 1:touchstart 1:touchend 2:touchstart 2:touchend (the actual desired behavior)
- (25%) 1:touchstart 1:touchend (and nothing at all for the second tap)
So if I don't hook the mouse events, it works a third of the time; and if I was willing to pretend that touchcancel
meant the same thing as touchend
, I could get that up to 75% of the time. But that's still pretty sucky.
Alternatives I've already tried:
- I've tried using jQuery Mobile's
vmousedown
andvmouseup
events, but they aren't always triggered for the second tap, I suspect because of this same underlying event weirdness. - I could just forget about touch events entirely and only use the synthesized mouse events, but there's usually about a half-second delay between the physical tap and the delivery of the synthesized mouse event, whereas the touch events are immediate so I can be more responsive. I also want to prevent scrolling -- this is for a fullscreen game, and I'd rather not have the user accidentally scrolling the address bar back into view and blocking part of the game -- and doing
preventDefault
on thetouchstart
usually achieves that (though occasionally the second tap is actually able to scroll the screen despite mypreventDefault
... another reason I want to solve this whole event mess). - I've tried a third-party Web browser (Dolphin), but it has the same problems with events. So I'm guessing it's probably a problem with the way the underlying WebView delivers events to scripts.
Can anyone suggest a way to change my HTML, my event handlers, or anything else in order to reliably get touch events for two quick taps in succession?