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