Skip to content
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

Mismatch between scroll and focus handling #231

Closed
jakearchibald opened this issue May 20, 2022 · 5 comments · Fixed by #239
Closed

Mismatch between scroll and focus handling #231

jakearchibald opened this issue May 20, 2022 · 5 comments · Fixed by #239

Comments

@jakearchibald
Copy link

The default focus behaviour is to reset focus, which is appropriate behaviour if the page is changing significantly, but not in cases like tabbed navigation.

This behaviour was decided in #202 to match MPA behaviour.

However, it seems like current scroll behaviour is out of step with this intent. In a regular MPA, when you navigate via either push/replace, scrolling is reset to 0,0. It feels like this should happen here too.

I'm not sure if this should be rolled into the current scroll option, or whether it needs a separate scrollReset option.

@domenic
Copy link
Collaborator

domenic commented May 23, 2022

This came up in discussion with @annevk as well; it slipped out of my brain before I could file it. Thanks for finding this and bringing it up.

The desire for symmetry between scroll and focus is indeed what makes this hard. A quick fix would be to add a scrollReset option which only applies to push/replace/reload (or maybe only push??). And maybe that's the right thing to do. But if we want to take a step back the picture gets more complicated...

The cross-document nav experience is:

  • Scroll resets on push/replace
  • Scroll restores on reload/traverse
  • Focus resets on push/replace/reload/non-bfcache traverse
  • Focus restores on bfcache traverse

The current navigation API default experience for same-document navs is:

  • Scroll stays the same on push/replace/reload
  • Scroll restores on traverse
  • Focus resets on push/replace/reload/traverse

We've been treating focus restore as something that we can't do by default, because of the problem of identifying the "same" DOM element. That seems like a safe bet to me. But we could probably fix the scroll reset/restore behavior.

Proposal:

  • A separate scrollReset: "after-transition" or "manual", default "after-transition". This applies to push/replace.
  • Update scrollRestoration: "after-transition" to apply to reload. This might be a bit difficult since same-document "reloads" are basically no-ops right now, but hopefully we can try to make it work.

These are both compat-impacting changes. They would bring us to

The proposed navigation API default experience for same-document navs is:

  • Scroll resets on push/replace
  • Scroll restores on reload/traverse
  • Focus resets on push/replace/reload/traverse

which is close to the cross-document nav experience.

@jakearchibald
Copy link
Author

Scroll resets on push/replace

The case I'm unsure about is hash-change navigations.

I'm happy with scrollReset as an option. The only reasons I thought about rolling them into one option is that scroll resetting is a little bit like a restoration, but it's restoring it to 0,0. You never get resetting and restoring happening on the same navigation.

@domenic
Copy link
Collaborator

domenic commented May 26, 2022

The case I'm unsure about is hash-change navigations.

If you call transitionWhile() on a hashchange navigation, you are opting out of the usual navigate-to-a-fragment anyway. (This is why a lot of the demo code has if (!e.hashChange).)

Whereas, if anyone was using the hash to store information that was not a target element (e.g., UI state like which tab was open) they might want resetting behavior...

So I think it would be OK.

You never get resetting and restoring happening on the same navigation.

That's a good point, and it would be nice to unify. However:

  • I can't think of any good name that covers both resetting and restoration. Can you?
  • You might want to control them individually. This is easy if they're separate, e.g. { scrollRestoration: "manual", scrollReset: "after-transition" }. It's harder if they're combined; you need do to { bikeshed: e.navigationType === "traverse" || e.navigationType === "reload" ? "manual" : "after-transition" }.

These are not very strong objections, so if we can think of a good name it still might be better to combine.

@jakearchibald
Copy link
Author

I can't think of a good name either. Given the above, it seems fine for them to be seperate options.

@domenic
Copy link
Collaborator

domenic commented Jun 10, 2022

#237 is about a different problem, but its solution will probably be part of the same API, and subsume what we're talking about here. So people interested should follow both issues.

domenic added a commit that referenced this issue Jun 15, 2022
This changes the default behavior of intercepted navigations with respect to scrolling in the following ways:

* Scroll restoration is performed on reloads, not just traverses.
* For pushes and replaces, we either scroll to the top or scroll to the fragment.

This change requires some updated API surface: the scrollRestoration option to intercept() has become scroll, and the navigateEvent.restoreScroll() method has become navigateEvent.scroll().

Closes #237 by giving an easy default behavior for SPA navigations with hashes.

Closes #231 by making "push", "replace", and "reload" behave by default in an MPA-like manner with respect to scroll position, just like "traverse" does.
domenic added a commit that referenced this issue Jun 15, 2022
This changes the default behavior of intercepted navigations with respect to scrolling in the following ways:

* Scroll restoration is performed on reloads, not just traverses.
* For pushes and replaces, we either scroll to the top or scroll to the fragment.

This change requires some updated API surface: the scrollRestoration option to intercept() has become scroll, and the navigateEvent.restoreScroll() method has become navigateEvent.scroll(). The latter method now has more capabilities, working to give browser-default-like behavior for "push", "replace", and "reload" in addition to "traverse".

Similar to before, all of this can be opted out of by using scroll: "manual".

Closes #237 by giving an easy default behavior for SPA navigations with hashes.

Closes #231 by making "push", "replace", and "reload" behave by default in an MPA-like manner with respect to scroll position, just like "traverse" does.
domenic added a commit that referenced this issue Jul 13, 2022
This changes the default behavior of intercepted navigations with respect to scrolling in the following ways:

* Scroll restoration is performed on reloads, not just traverses.
* For pushes and replaces, we either scroll to the top or scroll to the fragment.

This change requires some updated API surface: the scrollRestoration option to intercept() has become scroll, and the navigateEvent.restoreScroll() method has become navigateEvent.scroll(). The latter method now has more capabilities, working to give browser-default-like behavior for "push", "replace", and "reload" in addition to "traverse".

Similar to before, all of this can be opted out of by using scroll: "manual".

Closes #237 by giving an easy default behavior for SPA navigations with hashes.

Closes #231 by making "push", "replace", and "reload" behave by default in an MPA-like manner with respect to scroll position, just like "traverse" does.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants