Touch Event behavior details across browsers
This document attempts to collect details of the precise behavior of touch events on different browsers. Mostly (but not always) these are things that are underspecified by the specification. The primary goal is to help determine the direction for blink for maximum performance and compatibility.
Rick Byers
Last updated June 12, 2014
Touchevents while Scrolling
Touch event timeouts
TouchEvent preventDefault impact on click events
Handler addition interaction with in-progress touches
Events sent during scroll start slop
Touchend preventDefault on scroll
Timing of touchmove events relative to vsync
Touchevents while Scrolling
The precise behavior of touch events while touch-scrolling a page or div varies greatly between browsers with a variety of tradeoffs between performance and functionality. There are 4 basic models:
- Normal synchronous event processing
touchmove events are sent during scrolling and each scroll update blocks until touchmove handling has completed (to see if JavaScript calls preventDefault on the event). This is bad for scroll performance - it means each frame during the scroll must block on the main thread. See www.rbyers.net/janky-touch-scroll.html for a demo of this jank. A maximum timeout can be applied to each event (see next section) to bound the impact of this problem.
- Asynchronous touchmove processing
touchmove events are sent as normal, but scrolling can proceed asynchronously (and so the disposition of the touchmove event is ignored after scrolling has begun). The main disadvantage here is that events may be 'double handled' causing subtle bugs. The website does something with the touchmove and calls 'preventDefault' on the event telling the browser not to handle it, but the browser ignores that and continues to scroll anyway (or triggers an overscroll action like back/forward).
- To help mitigate double handling, Event.cancelable could be set to false for async touchmove events. This can be a signal to pages that it’s too late to stop scrolling, and is technically the correct thing to do anyway.
- Touchmove events suppressed while scrolling
When scrolling starts, suppress sending touchmove events (and send touchend on lift as normal). The main disadvantage is that a site can’t tell the difference between scrolling and holding a touch stationary (eg. in order to cancel some affordance or pending tap-action). In some cases 'scroll' events can be used to get async notification that scrolling is happening (and has taken control of the events), but ‘scroll’ events don’t bubble so additional handlers may be necessary, and they will not be fired when no scrolling actually occurred. Also some existing code expects to be able to track fingers even during a scroll. Chrome for android considered this model but rejected it based on feedback from app developers.
- Touchcancel on scroll start
When scrolling starts, send 'touchcancel' and no further events. This approach makes it clear to web developers that there is a tradeoff to be made between (smooth) scrolling and event handling, and they can’t really have both. This is embodied explicitly in the design of pointer events (used by IE10+, and which Firefox has said is the future of their input system). The disadvantage is that this behavior is specific to Chrome Android and many sites aren't prepared for it (eg. don't properly listen to touchcancel and so believe a finger is stuck down, eg. see here), although these are generally bugs that are possible (but less likely) to hit on other browsers too. There are also some scenarios that are not possible in this model (following the finger while scrolling like to enable pull-to-refresh style UIs).
Test pages
Chrome desktop
- except there’s currently a bug where the event disposition is ignored
- this means we had the worst of both works (poor performance and double handling)
Chrome Android
Android Browser
- Normal synchronous event processing (with timeout)
- Eg. scroll jank test with delay of 100ms shows jank throughout scrolling
- Once scrolling has been prevented it does not start/restart for that touch
- But it is possible to stop it arbitrarily at any point
- Tested on Galaxy Nexus Android 4.3
- Android 4.0.4
Mobile safari:
- asynchronous touchmove processing (with TouchEvent.cancellable==true)
- but document scrolling scrolls a static image of the page, so updates as a result of the events aren’t visible until you lift your finger
- if there is nothing to scroll in the horizontal direction then you’ll see events come in as you drag (in contrast to vertical scrolling which will always trigger overscroll)
- note that the start of scroll can still be delayed by jank - see http://jsbin.com/IsisIso/1
- scrolling a div (with or without -webkit-overflow-scrolling:touch)
- scrolling a div without -webkit-overflow-scrolling: touch
- you can turn scrolling on and off at any point during the gesture by consuming touchmove events or not
- when scrolling is turned off, scroll deltas are still accumulated
- when the first touchmove is consumed, scrolling can still be started later
- this is in conflict with the W3C TouchEvents specification which says: “If the preventDefault method is called on the first touchmove event of an active touch point, it should prevent any default action caused by any touchmove event associated with the same active touch point, such as scrolling.”
- scrolling a div with -webkit-overflow-scrolling: touch
- once the decision has been made to scroll or not, it cannot be changed
- touchmove events are still received throughout the scroll (and block the scroll), but their disposition is ignored (i.e. the worst of both worlds)
Firefox
- Asynchronous touchmove processing (with TouchEvent.cancellable==true)
Samsung internet
- Reports itself as Chrome 18.0.1025.308
- Touchmove events suppressed while scrolling
- mousemove events sent during active scroll (but not on overscroll)
Touch event timeouts
Test pages:
Chrome Android - background
- If touch event isn’t handled within 200ms, start processing it anyway as if preventDefault wasn’t called and send touchcancel
- In M29 added limitation that it no longer applies after preventDefault has been called on a previous touchstart/touchmove
- In M32 added limitation that it doesn’t apply to ‘mobile’ sites (those with fixed-width viewports), and in M34 to any width=device-width pages:
- Complaints about this behavior:
Safari
- As of iOS 8 appears to have a variable timeout
- With ‘show log’ disabled in the test page (to keep things simple), Safari appears to wait ~5,000ms before giving up on touch handlers and starting scrolling
- But with show log enabled, the behavior appears somehow adaptive. A timeout can occur within 500ms-750ms and once it’s hit the timeout appears to be very short (<1 ms) for all future scrolls.
- If a touch event is consumed within this very short time period, then the system resets to it’s initial state (long timeout)
- There’s is also clearly at least one bug here (page moving when touched without dragging)
- Prior to iOS 8 In my tests, anything above about 3000ms starts to behave strangely
- the event stream is interrupted - never see a touchend
- strange scrolling behavior (document scrolls when my touch was over a scrollable div, scrolls one direction then back the other when my gesture was only in one direction, etc.).
Android browser
- 200ms timeout for every touch event
- as soon as one event takes >200ms, a touchcancel is sent and no further events are sent for the touch
Firefox
TouchEvent preventDefault impact on click events
In most modern browsers, calling preventDefault on any touchstart or touchend event will prevent getting a click event (the spec requires this behavior on at least touchend). But what about any intervening touchmove events? If the touch sequence would normally be registered as a click, does calling preventDefault on the touchmove prevent that? In particular, can a web developer reliably disable scrolling without disabling click events by calling preventDefault on touchmove events.
Chrome
- N/A - chrome currently suppresses touchmove events during the slop region, so click will NEVER involve a touchmove
Mobile Safari
- preventDefault on touchmove DOES disable click events
Handler addition interaction with in-progress touches
Consider a touch that goes down in an area that has no touch event handlers whatsoever, but while the touch is down a handler (say touchmove) is added. Does that handler start receiving events for the previously active touch? Also the spec doesn’t say so explicitly, I think you’d assume that you would receive such events. However it’s unclear whether receiving these events is more or less likely to trigger bugs in websites (eg. it’s not uncommon for developers to assume that they see a touchstart for every touchend they get).
Chrome (desktop and Android)
- Does NOT dispatch events for touches which had no handlers at touchstart time.
- This is really a side-effect of a performance optimization in compositor thread hit testing. Once the compositor thread tells the browser NO_CONSUMER_EXISTS for a touch, the browser doesn’t bother to send any further events for that touch (which can decrease latency for touch scrolling).
Events sent during scroll start slop
When the user is starting to scroll there is some amount of movement permitted before scrolling starts. What happens to those movement events? At the very least one movement event must be sent just before scrolling starts, to give the application the chance to disable scrolling after something is known about the motion (eg. that it’s a scroll not a tap, or even which direction the scroll is moving).
The main reason not to send events for the slop is that it makes it easier for sites to know reliably what the last move event is before scrolling will start. I.e. sites can easily disabling scrolling without disabling click events (etc.), and can also decide whether or not to enable scrolling based on the direction of the movement (although this is probably better addressed by touch-action now). Another benefit to not sending events for the slop is that it may decrease the time necessary to start a scroll (due to having more events blocked on the main thread).
The main reason to send the slop events is that it accurately represents the data from the hardware and leaves full interpretation of that data to the application. Certainly a paint application (and probably some games) will want to be able to draw even tiny bits of motion. It doesn’t seem fair to require such appliactions to disable click events (by calling preventDefault on the touchstart). It’s also potentially confusing to developers if they happen to “get more events” as a result of consuming a touchstart.
Chrome Android
- But may reconsider now that we no longer send touchcancel
Chrome Desktop
- Always sent events for the slop prior to M4
- As of M34, matches Chrome Android behavior
Android browser
Safari
- Always sends events for the slop
- Note that if you move too slowly you’ll trigger the long press gesture and then scrolling will be impossible.
- tested iPad 4 Safari, iOS 7.0.2
Firefox
Blackberry
Test Pages
- Note that it’s possible that slop could be consumed below the browser (eg. in the OS or touchscreen firmware). To confirm this isn’t the case, verify you can see slop with Chrome M33 or greater on eventTest with touchstart preventDefault enabled.
Touchend preventDefault on scroll
When scrolling, does the disposition of a touchend event have any impact? Does the browser need to block the start of fling on it?
Chrome
- Prior to M35 (touchmove absorption), always got a touchcancel on scroll so there would be no touchend event at all.
- As of Chrome M35, consuming touchend does NOT prevent fling (fling still delayed for it due to a bug).
Safari
- Consuming touchend does NOT prevent fling. Consistent with the fact that once native scrolling has started, it runs freely without blocking on JS
Android browser
- Consuming touchend prevents fling
- but note that the timeout behavior means it would be hard to rely on this in practice
Firefox
- Consuming touchend does NOT prevent fling
Test pages
Timing of touchmove events relative to vsync
When dragging the finger, when do the touchmove events come relative to, for example, requestAnimationFrame callbacks?
Chrome
- On Android: the Android SDK buffers all input and aligns it to vsync (interpolating or extrapolating finger position if necessary). TODO: details / code links.
- touchmove events tend to arrive shortly after requestAnimationFrame callbacks, but blink doesn’t guarantee this
- On other platforms: depends on the OS, generally unaligned.
- But we’re considering mimicking the Android behavior within Chrome on all platforms
Safari
- Appears to be aligned with vsync - arrive shortly after rAF callbacks
Edge
Test pages