Introduction
Most browsers implement a set of navigations actions that are triggered by user gesture (find a list below). The user-agent relies on heuristics^ to detect if the scroll gesture should navigate or whether it should be handled by the page. These heuristics may work in general case but fail for certain applications. Such failures are especially problematic because a navigation action is a major context switch that disrupts user experience.
Over time, web developers have come up with 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. At Chromium, we have heard from web developers about the need for a simpler and more reliable way to do this. This document proposes a standard CSS based API which replaces many of these workarounds.
A survey of current gesture navigations:
- Pull-to-refresh: Mobile Chrome and Opera
- Over-scroll swipes: Chrome, Desktop Safari, Desktop Firefox, IE, Edge^^
- Scroll from edge: Mobile Safari
^: For example, requiring the scroll gesture to be of certain magnitude, be in certain direction, or start when scroller is at its limit.
^^: Current stable release of Microsoft Edge has no gesture navigation but they are adding support for overscroll swipes due to popular demand.
Pull-to-Refresh (p2r) Problem
Pull to refresh is a feature of Mobile Chrome (and Opera) where once the user over-scrolls the page we show a refresh animation and refresh 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 certain mobile web applications and can be disruptive and costly.
Below is a list of few (see this stackoverflow thread for more) workarounds used by apps to disable pull-to-refresh behaviour which come with their own negative consequences:
- Disable document scrolling, move all content in a scrollable div
- Cons: forces a certain layout and loses top controls behaviour.
- 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.
- 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 Problem
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 over-scrolls 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.
There are several workarounds but as similar to p2r ones they are not simple and have draw-backs and none works in Mobile Safari.
- Prevent-defaulting touch event handlers (or wheel event for desktop browsers) similar to p2r.
- 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
Examples of developers trying to find a solution:
- disabling swipe navigation on ie10, and safari
- disabling overscroll without touchstart
- disable webpage navigation on swipe back
Out of Scope
Navigation gestures that do not need to compose with page content, e.g., swiping URL bar.
Also cosmetic over-scroll 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 API does not prohibit its extension in order to cover that case if there is interest.
Proposed API: overscroll-action
Introduce a new non-inherited CSS property overscroll-action, that may be applied on the root element and applies to the viewport. It controls the permitted overscroll actions by the browser on each axis. Each property accepts two values that control the permitted overscroll on its individual physical directions i.e., left, right, and top, bottom.
overscroll-action-{x,y}: [auto || none]{1,2}
-
auto
: the user agent determines appropriate default action for overscrolls in this axis and direction. -
none
: overscroll in this axis and direction should not trigger default action such as navigation. This does not impact UI effects such as bounce or glow.
Allows controlling x and y axis separately e.g., disable p2r without affecting gesture nav. This means that different components may control the behaviour on each axis without having to coordinate (e.g., a routing library may want to control vertical overscroll, and a carousel may want to control horizontal overscroll).
The idea for using a CSS property is originally mentioned here. It is simple to understand and use.
Note that this property does not prevent us from later adding corresponding javascript event that allows customization of p2r.
Syntax alternatives:
- Shorthand syntax:
overscroll-action: [auto || none] {1,4}
. A shorthand syntax can be used to control both axis in a single property. Allowing this syntax has the risk of encouraging developers to naively useoverscroll-action: none;
as a quick way to disable p2r and inadvertently disable swipe navs. - Explicit action syntax:
overscroll-action: [auto || [navigate-x | navigate-y] || none]
. This syntax forces developers to be explicit about the actions that they want to prevent. This can be useful if we expect multiple different overscroll actions that the application want to individually control. Also perhaps easier to expand if we want more granular control such as per-direction (e.g., navigate-left vs navigate-right).
overscroll-action
for inner-scrollables
Non root scrollables may have default overscrolling actions such as glow or bounce effects. The current proposal only intends the overscroll-action property to be applicable on the root scroller and cover navigation actions. Expanding this property to be applicable to non-root scrollers for controlling glow, or bounce effects is outside the scope of this proposal.
Feature Detection and Polyfillability
It is should be rather straight forward to detect existence of CSS property. A polyfill may fallback to one of the existing workarounds which are unfortunately unreliable and have limited applicability.
Alternative Solutions
- Viewport Meta Tags: A CSS, or Javascript based solution is preferable to meta tags.
- App Manifest: This was rejected as being out of scope for app manifest.
- Refresh Event (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 actions to the main thread (aka slow path) which can introduce delay into navigation process.
- 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 more natural.
Customizing vs Disabling P2R
Some web applications (e.g., twitter) may be interested in keeping the p2r UI affordance but hook their own custom refresh logic replacing the browser’s default. This is certainly a valid usecase and should also be addressed. However we think it is worth addressing these two separately for the following reasons:
- 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.
- Once we have a reliable way to disable UA’s behaviour, it becomes much easier to create customization on top.
- p2r is a Chrome only feature at the moment. This proposal focuses on a solution that is useful for both p2r and swipe navigations across browsers.
- There is some awkwardness in addressing both the Disable and Customize usecases using a single event e.g., deciding whether p2r animation should be shown or not.
Links
- Chrome Pull-to-Refresh design document by jdduke@
- Refresh event www-dom proposal by rbyers@
- Proposal for a property on app manifest on w3c-manifest by jdduke@
- Chromium bug tracking our progress