We propose a new CSS property `overscroll-action` to control the allowed navigation actions for overscroll gestures. This enables web applications to opt-out of certain navigation gestures that may be disruptive to their UI.
We actively solicit comments on this document either via comments to [WICG discourse thread][wicg-thread] or opening an issue on [GitHub][github-repo] for this repository.
## Motivation Most browsers implement a set of navigations actions that are triggered by user gesture (almost always an "overscroll"). The user-agent relies on heuristics to detect whether the gesture should result in a navigation or should be handled by the page. These heuristics may work in general case but fail for certain applications. Such failures are particularly problematic because a navigation action is a major context switch that disrupts user experience. Over time, web developers have resorted to a set of workarounds to disable these navigation gestures either to prevent said disruptions or to implement their own customized versions. Unfortunately these workarounds are not always reliable and have major drawbacks. We believe a simple and reliable way to control these navigation gestures will go a long way to address this platform pain point. A survey of current navigation gestures: * **Pull-to-refresh**: Mobile Chrome and Opera * **Overscroll swipes** : Chrome, Desktop Safari, Desktop Firefox, IE, Edge^^ * **Scroll from edge**: Mobile Safari ^^ Current stable release of Microsoft Edge has no navigation gesture but they are adding [support ][edgeHTML-feature] for overscroll swipes due to popular demand. ## Usecases ### Disabling Pull-to-Refresh Pull to refresh is a feature of Mobile Chrome (and Opera) where once the user overscrolls the page, the browser shows a refresh animation and refreshes the page. The UX pattern was popularized by native mobile applications as a way to fetch new content which in the context of the browser translates to refreshing the page. However refreshing the page does not make sense and is not relevant for some mobile web applications and can be disruptive and costly. [Here][p2r-workarounds] is one example of developers trying to find solution to this. Appendix contains a list of existing problematic workarounds for this. ### Disabling Swipe Navigations Most major mobile and desktop browsers provide a swipe navigation feature where they navigate backward and forward in history when a user swipes left or right and overscrolls the page. In some page layouts (e.g., a page with edge-to-edge horizontal image carousel) it is very easy for a user to trigger these navigation actions accidentally once they have reached the page extent. Examples of developers trying to find a solution [here][swipe-workaround1], [here][swipe-workaround2], [here][swipe-workaround3], and [here][swipe-workaround4]. Appendix contains a list of existing problematic workarounds for this. ## Proposed Syntax We introduce a non-inherited CSS property `overscroll-action`, that may be specified on the root element and applies to the viewport. It controls the permitted overscroll actions by the browser. `overscroll-action: [auto || [no-navigation-x | no-navigation-y]]` * `auto`: The user agent determines appropriate default action for overscrolls. This is the default value. * `no-navigation-x`: overscroll on x axis should not cause any navigation such as back or forward history navigation. This value does not impact UI effects such as bounce or glow. * `no-navigation-y`: overscroll on x axis should not cause any navigation such as a page refresh. This value does not impact UI effects such as bounce or glow. This syntax makes it so that the author has to opt-out of specific actions that they want to prevent. This improves forward compatibility and makes it easier for user-agents to add additional overscroll actions or finer grained control, e.g., per-direction, in future. ## Out of Scope ### Cosmetic overscroll effects Cosmetic overscroll effects such as glow, or bounce are out of scope of this proposal. Unlike navigation, these affordances do not cause a drastic context-switch and usually play a role in keeping platform UI consistent so browsers may not be willing to expose a way to disable them. However this proposed syntax for CSS property does not prohibit its extension in order to cover that case as long as we expand it to non-root scrollables. ### Navigation gestures outside page content Navigation gestures that do not need to compose with page content, e.g., swiping URL bar, are outside the scope of these proposal. Web pages do not need to (and perhaps should not) control these gestures ## Feature Detection and Polyfillability Detecting existence of a CSS property is straight forward. A polyfill can use such detection and fallback to using one of the existing workarounds which are unfortunately unreliable and have limited applicability. ## Customizing vs Disabling Some web applications (e.g., twitter) may be interested in keeping the pull-to-refresh UI affordance but hook their own custom refresh logic replacing the browser's default. This is a different but certainly a valid use-case which is best addressed by an event. We think we should address these two cases separately starting with the disable use-case for the following reasons: 1. The cost of not being able to disable seems to be greater than not being able to customize. Also anecdotally it appears that most apps just want to disable rather than customize so we want to make sure there is a simple API for that. So it is important to address disable first. 2. Once we have a **reliable** way to disable UA's behaviour, it becomes much easier to create customization on top. 3. A synchronous event has unacceptable performance characteristic for any UI effect, such as scrolling, that needs to be smooth and potentially run in a separate thread. In these cases we need to know before scrolling starts what effect it can trigger. 4. There is some awkwardness in addressing both the disable and customize use-cases using a single event e.g., deciding whether pull-to-refresh animation should be shown or not. ## Links * Chrome [Pull-to-Refresh design document][p2r-design] by jdduke@ * Refresh event [www-dom proposal][www-dom-proposal] by rbyers@ * Proposal for a [property on app manifest][manifest-proposal] on w3c-manifest by jdduke@ * [Original suggestion][css-proposal-origin] for using a CSS property. * Chromium [bug][crbug] tracking our progress ## Appendix ### Pull-to-refresh existing workarounds Below is a list of few workarounds used by apps to disable pull-to-refresh behaviour which come with their own negative consequences: 1. Disable document scrolling, move all content in a scrollable div * Cons: forces a certain layout and loses top controls behaviour. 2. Disable vertical scrolling all together using ‘touch-action: none;’ * Cons: Opting out of the user-agent scrolling machinery is over-kill and adds a lot of complexity to the application. 3. Add touch event handlers and prevent scroll if gesture is scrolling vertically in a way that induces a refresh (example: AMP implementation) * Cons: touch event handlers has perf implications for threaded scrolling and should be avoided if possible. Also this can become flaky if the app hits touch ack timeout. ### Swipe navigation workarounds There are several workarounds but similar to pull-to-refresh these are either unreliable, or complicated, or have performance drawbacks, or a combination of these. Also none of these work in Mobile Safari. * Prevent-defaulting touch event handlers (or wheel event for desktop browsers) similar to pull-to- refresh. * Using `touch-action: pan-y` on document to prevent horizontal (or vertical pan) * Use scroll chaining: This is an IE specific workaround and disables scroll chaining (`--ms-scroll- chaining: none`) on root scroller. This actually does not work well in practice as it only prevents overscroll action if the page is not scrolled fully to its extent and requires the page to be scrollable ### Alternative Solutions Considered * Viewport Meta Tags: A CSS, or Javascript based solution is preferable to meta tags. Such a solution only applies to viewport overscrolling and currently the viewport meta tag is not very well specified and supported consistently across browsers so it is not clear if it is a good idea to add more logic to it. * [App Manifest][manifest-proposal]: This was rejected as being out of scope for app manifest. * Refresh Event ([www-dom proposal][www-dom-proposal] by rbyers@): This seem to be favored by some web developers as it provides a path to customize the "refresh" action and not just disabling it. * Does not cover swipe navigations but perhaps a more generic navigation event can be used. * This ties navigation action and its UI affordance to the main thread (aka slow path) which can introduce delay into navigation process and prevent smooth UI affordance. * Mozilla engineers were concerned that such an event should be applicable to all refresh affordances on all platforms with an escape hatch for hard reloads and not just p2r. * See additional discussion below on Disabling vs Customizing. * Attributes on navigator (e.g., Navigator.refreshEnabled, Navigator.swipeNavigationEnabled): This works fine but it seems a CSS property is a more natural way to control this. ### Alternative Syntax Considered `overscroll-action-{x,y}: [auto || none]` This syntax mimics overflow-{x,y} syntax. It is simple and allows controlling x and y axis separately making it easier for different components to control the behaviour on each axis without coordination (e.g., a routing library may want to control vertical overscroll, and a carousel may want to control horizontal overscroll). However it lacks the flexibility and fine grained control of the proposed syntax. [wicg-thread]: https://discourse.wicg.io/t/proposal-api-to-control-user-gesture-navigation/1536 [github-repo]: https://github.com/majido/user-gesture-nav-proposal [p2r-design]: https://docs.google.com/document/d/12Ay4s3NWake8Qd6xQeGiYimGJ_gCe0UMDZKwP9Ni4m8/edit# [crbug]: https://bugs.chromium.org/p/chromium/issues/detail?id=465423 [css-proposal-origin]: https://github.com/w3c/manifest/issues/398#issuecomment-133478796 [manifest-proposal]: https://github.com/w3c/manifest/issues/398 [www-dom-proposal]: https://lists.w3.org/Archives/Public/www-dom/2015AprJun/0022.html [edgeHTML-feature]: https://blogs.windows.com/windowsexperience/2016/05/16/announcing-windows-10-mobile-insider-preview-build-14342/ [p2r-workarounds]: http://stackoverflow.com/questions/29008194/disabling-androids-chrome-pull-down-to-refresh-feature [swipe-workaround1]: http://stackoverflow.com/questions/13654238/can-i-disable-ie10-history-swipe-gesture/27935785#27935785 [swipe-workaround2]: http://stackoverflow.com/questions/18889666/ios-7-is-there-a-way-to-disable-the-swipe-back-and-forward-functionality-in-sa [swipe-workaround3]: http://stackoverflow.com/questions/23560528/how-to-prevent-overscroll-history-navigation-without-preventing-touchstart [swipe-workaround4]: http://stackoverflow.com/questions/30636930/disable-web-page-navigation-on-swipeback-and-forward