-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Better control over scroll restoration during traversal #187
Comments
Labeling "Might block v1" specifically for the part
since changing that default would probably cause compat issues. |
I really like the idea of being able to attach scroll restoration events to any element. That's one of the problems we have now: we can only restore the scroll on the window element. If the user wants to scroll a different element, they have to do it manually. Something that worries me is not being able to fully control when the event triggers: I can delay the transitionWhile but in a data driven approach, the navigation finishes to update the route location, which in turn triggers the DOM update, and only then the scrollBehavior (a custom function passed to the router instance in the case of vue router) would trigger. I could definitely update the route location that triggers the DOM update and wait to finish the navigation but that will mean that the DOM update would happen before the navigation has finished, potentially letting the user read from old history entries. Currently, in Vue Router we rely on manual scroll restoration because it gives us the flexibility to integrate it with Vue. I imagine other frameworks do the same. |
I'm not sure I understand the concern in your second paragraph, probably because of lack of familiarity with Vue Router. First, the intent is that you do kinda get full control over scroll restoration: for the Second, in general we want to encourage the promise passed to respondWith() to not settle until the DOM updates are done. That is, the browser spinner should not stop spinning, and accessibility tech should not announce the navigation is finished, and so on, until all the appropriate content is in the DOM and settled down. But it sounds like that might cause some concern about "letting the user read from old history entries"? I don't understand that concern. Looking forward to your thoughts. |
I think this clears up everything to me and it makes total sense. About PS: you are referring to |
It is being newly-proposed in this post :)
Yes, sorry about that! transitionWhile() is indeed correct. |
Apparently the This proposal has the same problem! If we fire I will try to think if there is a way around this... |
I've specced out a minimal version of this, with only app history integration and only |
Talking with a few web developers, we've pinpointed a few issues with scroll restoration during traversal, that seem relevant to any history/navigation API.
The essential problem we've heard so far is that scroll restoration happens unpredictably and often at the wrong times. For example:
Note that these problems are present for both cross-document traversals and same-document traversals.
The developers I've talked to have expressed that they're looking for something between the two extremes we currently have available:
history.scrollRestoration = "manual"
)Here's an initial stab at a solution for this problem space. Note that the core proposal is not really tied to the rest of the app history API, and might end up advancing as a separate proposal on the whatwg/html repo. But I wanted to start with this community, since it fits into the general problem space of "ideal new APIs for handling history/navigation". And I do have an app history specific bonus proposal below the main one.
Proposal
Introduce a new event,
beforescrollrestoration
. It can be targeted at any scrollable container, with the<html>
element being the one for the overall viewport. (Or, the<body>
element, in quirks mode...) But e.g. if you were implementing your page as one large scrollable<div>
inside a non-scrolling viewport, then you'd listen for the event on that<div>
.This event would fire whenever the browser would normally perform scroll restoration. Per the current spec and tests, is after
popstate
/beforehashchange
. (App history'scurrentchange
comes beforepopstate
, so the total sequence woul be:currentchange
,popstate
,beforescrollrestoration
,hashchange
.) But, see below...The event has the following methods:
preventDefault()
: stops scroll restoration from happening, for nowgetDestination()
: returns a{ scrollLeft, scrollTop }
pair corresponding to where the browser would currently restore the scroll position, if scroll restoration were to happen right now.restore()
: performs scroll restoration, essentially equivalent toThe key thing about this event is that its
getDestination()
andrestore()
methods can still be used, in the future, even afterpreventDefault()
was called. So here is an example use:Note in particular that the value
getDestination()
returns can change over time, as a result of this sort of delay. E.g., if the browser has stored internally something like "100px from the top of the#foo
element", if the#foo
element is not in the DOM atbeforescrollrestoration
time, it will probably return (0, 0) or some other inaccurate value. But if, by the time we get to thedoScrollRestoration
function, the page has done all its client-side rendering and put#foo
into the DOM,getDestination()
will return some useful value, andrestore()
will restore the scroll position to that useful location.Potentially at some point (such as if the user ever starts scrolling?) these methods should stop working, e.g. returning
null
forgetDestination()
and doing nothing when you callrestore()
. At least in Chromium we abort any attempts at scroll restoration if the user starts scrolling plus a few other conditions I haven't fully understood yet.Also note that this event fires both for same-document navigations, and cross-document navigations.
App history-specific bonus proposal
The above seems like the right primitive that should allow developers all the control they need. (At least, in cases where they don't want to just handle scroll restoration completely manually, using
history.scrollRestoration = "manual"
.)But for the specific case of same-document transitions, we can leverage the
navigate
and the promise passed totransitionWhile()
to handle things a bit more automatically. I'm thinking that by default, whenever you usetransitionWhile()
, we delay scroll restoration until the promise settles. Since the promise should ideally be settled when everything is loaded and the DOM is ready, this will probably work well in most cases.Doing so takes the scroll restoration process, as well as its corresponding
beforescrollrestoration
event, out of the normal ordering mentioned above.We'd let you opt out of this, using something like the following:
This doesn't solve all the scroll restoration problems:
transitionWhile()
(and, more generally, thenavigate
event fires in the context of the old document, not the new one)To solve the latter, you could mix in the
beforescrollrestoration
event in a somewhat messy way:We could make this nicer by introducing
navigateEvent.scrollRestoration
, withgetDestination()
andrestore()
methods, wheneverscrollRestoration: "manual"
is set. Then the above messy code could be rewritten asThe text was updated successfully, but these errors were encountered: