Non-Document Root Scrollers
Non document root scrollers is a proposed feature that would allow an author to endow an arbitrary scrolling element with the same specialness currently assigned to the documentElement.
Applications are typically composed of multiple views between which a user can transition. Traditionally, a web app would transition between its views by navigating to a new page. This causes a round trip to the server and a jarring experience for the user. To improve performance and allow for pleasing transitions, the author may wish to keep multiple views within the DOM, removing the need to navigate to a new document.
Authors can do this today by making each view out of a viewport-sized, scrollable <div> element. This is the intuitive solution and allows the author to animate transitions between these views and manage them as independent components.
On the other hand, browsers have given essential UX features exclusively to one special element: the documentElement (<html>). Here are some examples of how <html> is special:
- URL-bar Hiding - To maximize the user's screen real-estate, the browser's URL-bar is hidden when the documentElement element is scrolled down.
- Overscroll Affordance - To let the user know the page content is fully scrolled, further scrolls generate a "glow" or "rubber-banding" effect. In Chrome, this only occurs on the documentElement.
- Pull-to-Refresh - To allow the user to quickly refresh the page, scrolling the documentElement up beyond the content top activates a refresh action in Chrome. Chrome disables this effect when the documentElement isn't scrollable.
- Spacebar to Scroll - Many browsers use the spacebar as a shortcut to scroll down by a page. This often only works for the documentElement.
- Tapping top of browser chrome - iOS Safari has this to allow the user to quickly scroll back to the top of the page but it targets only the documentElement.
Thus, authors have a choice: use the intuitive method and lose all these essential UX features, or swap the content of each view in and out of the documentElement. The latter turns out to be surprisingly difficult to implement in a portable way for all the reasons listed in the MiniApp example. To summarize, swapping content within a single scroller is complicated since the scroll offset is shared between two conceptual views; it requires tricks to keep the content of multiple views overlaid and we have to manually keep track of each view's scroll offset. Likewise, animating transitions becomes tricky since the animation must be timed carefully with the content swap. Simple behaviors which emerge naturally from using separate <div>s become difficult and complicated to implement when forced to share the special documentElement.
We need to provide authors some way to make the intuitive "each view is a div" solution workable. This means giving an arbitrary scroller the same powers as the documentElement; anointing it the root scroller. Lets make the documentElement less special. The web is the only UI framework that gives special powers to one, and only one, special scrolling element.
I propose an explicit mechanism to allow the author to specify which element should be treated as the root scroller and, importantly, when. This will go some way to explaining how the page interacts with browser UI and give authors a some ways to control those interactions.
Why not a heuristic?
One alternate proposal is to apply these "root scroller" semantics to any scroller that exactly fills the viewport. This avoids introducing a new API. While this may work for some situations, it doesn't enable all the kinds of use cases we want to support. In the example below, we overlay one viewport-sized scroller over another and cross-fade between the two. It's less clear which scroller we should designate in this case.
Using a heuristic would make it difficult to spec out all the edge cases and make interop between browsers more difficult. It also means authors couldn't feature detect. It's also less future friendly as we may wish to (in a future version of the API) allow non-fullscreen root scrollers.
- Add a
var myScrollerDiv = document.getElementById('myScrollerDiv'); document.rootScroller = myScrollerDiv;
If the set element is a valid(*) scroller, scrolling it should perform all the same actions as the browser performs for document scrolling. E.g. hiding the URL bar, showing overscroll effects, pull-to-refresh, gesture effects, etc. If
no element is set as the
document.rootScroller, the browser defaults to using the
behavior is unchanged from today.
(*) For some - yet undetermined - definition of valid. Chrome's experimental implementation requires the element to be scrollable and exactly fill the viewport.
When a page sets an <iframe> element as the root scroller, e.g:
<iframe id="myIframe" src="..."></iframe> <script>document.rootScroller = document.querySelector('#myIframe');</script>
The browser uses whichever element the document in the iframe set as the document.rootScroller (remember, if none
is set it defaults to the
document.scrollingElement). This nesting works recursively; the iframe could itself set
a child iframe as rootScroller. In this way, the root-most document can delegate root scroller responsibilities to
Here's a common application on the web: a (potentially-infinite) stream of items that lets you open one up when you click on it. Since the stream is infinite, navigating to a new page when you open an item means lots of work to recreate the stream when the user navigates back. What if we could keep the item-view in the DOM all along? Here's an example of how we'd do that with this proposal: