Skip to content

Conversation

@seanpdoyle
Copy link
Contributor

@seanpdoyle seanpdoyle commented Oct 17, 2023

Related to #37

Add support for preserving scroll changes from page to page. Controlled
by the presence of the <meta name="turbo-visit-scroll"> element in the
<head>.

To preserve:

<meta name="turbo-visit-scroll" content="preserve">

To reset:

<meta name="turbo-visit-scroll" content="reset">

@seanpdoyle
Copy link
Contributor Author

@jorgemanrubia in #1019 (comment), you mentioned skepticism related to making scroll preservation configurable:

Just like morphing, scroll preservation for regular navigation could make sense in other scenarios, but we believe those will be quite rare and are out of the scope of what we're addressing here. I'd like to keep both "scroll" and "morph" tied to "page refreshes" for now, as I think it makes for a more cohesive development story.

This PR has the potential to provide a solution for #37 that draws inspiration from other <meta> element-enabled configurations. Has your perspective changed since this was last proposed?

Comment on lines +86 to +92
get visitScroll() {
return this.getSetting("visit-scroll")
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current <meta name="turbo-scroll"> is meant to parallel the concept of a "refresh".

Originally, this PR proposed <meta name="turbo-scroll">. An alternative couldbe <meta name="turbo-render-scroll">. Are either of those preferable?

if (this.isPageRefresh(visit)) {
return (visit?.refresh?.scroll || this.snapshot.refreshScroll) === "preserve"
} else {
return this.snapshot.visitScroll === "preserve"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduces a subtle bug when using the back button.

Without this change, the back button restores scroll position; with this change it resets scroll to the top.

It's tied to performScroll:

performScroll() {
  if (!this.scrolled && !this.view.forceReloaded && !this.view.shouldPreserveScrollPosition(this)) {
    if (this.action == "restore") {
      this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop()
    } else {
      this.scrollToAnchor() || this.view.scrollToTop()
    }

    this.scrolled = true
  }
}

Before:

  1. On Page A
  2. Go to Page B
  3. Hit Back button
  4. Trigger performScroll:
    this.scrolled false
    this.view.forceReloaded false
    this.view.shouldPreserveScrollPosition(this) false
  5. All false -> Triggers this.action === "restore"
  6. Scroll position restored

After:

  1. On Page A: Has visitScroll === "preserve"
  2. Go to Page B
  3. Hit Back button
  4. Trigger performScroll:
    this.scrolled false
    this.view.forceReloaded false
    this.view.shouldPreserveScrollPosition(this) true ⚠️ because this.snapshot.visitScroll === "preserve"
  5. Not all false -> Doesn't trigger this.action === "restore"
  6. Scroll position not restored

Possible solution

Not sure if it's the cleanest, but I've added a guard to the top of shouldPreserveScrollPosition:

shouldPreserveScrollPosition(visit) {
  if (visit?.action === "restore") return false
  if (this.isPageRefresh(visit)) {
    return (visit?.refresh?.scroll || this.snapshot.refreshScroll) === "preserve"
  } else {
    return this.snapshot.visitScroll === "preserve"
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tvararu thank you for testing this and providing feedback. I've pushed up a change to incorporate the Visit action in the conditional. Could you try it again?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested it locally, works for me!

  • Fresh page navigation: Scroll is at top
  • Scroll down a bit, refresh: Scroll stays where it is
  • Navigate somewhere else: Scroll is at top
  • Use back button: Scroll restores to expected location

In an effort to overcome [test flakiness][], pass `--retries=5` to the
Playwright CLI calls in the project's GitHub actions `ci.yml`
configuration file.

[retires]: https://playwright.dev/docs/test-retries#retries
[test flakiness]: https://github.com/hotwired/turbo/actions/runs/19574784533/job/56057106175?pr=1040#step:10:70
Related to [hotwired#37][]

Add support for preserving scroll changes from page to page. Controlled
by the presence of the `<meta name="turbo-visit-scroll">` element in the
`<head>`.

To preserve:

```html
<meta name="turbo-visit-scroll" content="preserve">
```

To reset:

```html
<meta name="turbo-visit-scroll" content="reset">
```

[hotwired#37]: hotwired#37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants