Skip to content

Commit 20e9941

Browse files
afcapelseanpdoyle
authored andcommitted
Add control to preserve scroll during Drive visits
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: ```html <meta name="turbo-visit-scroll" content="preserve"> ``` To reset: ```html <meta name="turbo-visit-scroll" content="reset"> ``` [#37]: #37
1 parent e24e768 commit 20e9941

File tree

7 files changed

+142
-2
lines changed

7 files changed

+142
-2
lines changed

src/core/drive/page_snapshot.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ export class PageSnapshot extends Snapshot {
8787
return this.getSetting("refresh-scroll")
8888
}
8989

90+
get visitScroll() {
91+
return this.getSetting("visit-scroll")
92+
}
93+
9094
// Private
9195

9296
getSetting(name) {

src/core/drive/page_view.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,13 @@ export class PageView extends View {
6060
}
6161

6262
shouldPreserveScrollPosition(visit) {
63-
return this.isPageRefresh(visit) && (visit?.refresh?.scroll || this.snapshot.refreshScroll) === "preserve"
63+
const { refreshScroll, visitScroll } = this.snapshot
64+
65+
const scrollDirective = this.isPageRefresh(visit) ?
66+
visit.refresh?.scroll || refreshScroll || visitScroll :
67+
visitScroll
68+
69+
scrollDirective === "preserve"
6470
}
6571

6672
get snapshot() {

src/core/drive/visit.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,9 +331,11 @@ export class Visit {
331331
// Scrolling
332332

333333
performScroll() {
334-
if (!this.scrolled && !this.view.forceReloaded && !this.view.shouldPreserveScrollPosition(this)) {
334+
if (!this.scrolled && !this.view.forceReloaded) {
335335
if (this.action == "restore") {
336336
this.scrollToRestoredPosition() || this.scrollToAnchor() || this.view.scrollToTop()
337+
} else if (this.view.shouldPreserveScrollPosition(this)) {
338+
this.scrollToRestoredPosition()
337339
} else {
338340
this.scrollToAnchor() || this.view.scrollToTop()
339341
}

src/tests/fixtures/scroll_restoration.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@
1212
</style>
1313
</head>
1414
<body>
15+
<h1>Scroll Restoration: default</h1>
1516
<ul>
1617
<li id="one">One</li>
1718
<li id="two">Two</li>
1819
<li id="three">Three</li>
1920
<li id="four">Four</li>
2021
<li id="five">Five</li>
2122
</ul>
23+
<a id="default" href="/src/tests/fixtures/scroll_restoration.html">Default scroll</a>
24+
<a id="preserve" href="/src/tests/fixtures/scroll_restoration/preserve.html">Preserve scroll</a>
25+
<a id="reset" href="/src/tests/fixtures/scroll_restoration/reset.html">Reset scroll</a>
2226
</body>
2327
</html>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="turbo-visit-scroll" content="preserve">
6+
<title>Scroll Restoration</title>
7+
<script src="/dist/turbo.es2017-umd.js" data-turbo-track="reload"></script>
8+
<script src="/src/tests/fixtures/test.js"></script>
9+
<style>
10+
li {
11+
min-height: 50vh;
12+
}
13+
</style>
14+
</head>
15+
<body>
16+
<h1>Scroll Restoration: preserve</h1>
17+
<ul>
18+
<li id="one">One</li>
19+
<li id="two">Two</li>
20+
<li id="three">Three</li>
21+
<li id="four">Four</li>
22+
<li id="five">Five</li>
23+
</ul>
24+
<a id="default" href="/src/tests/fixtures/scroll_restoration.html">Default scroll</a>
25+
<a id="preserve" href="/src/tests/fixtures/scroll_restoration/preserve.html">Preserve scroll</a>
26+
<a id="reset" href="/src/tests/fixtures/scroll_restoration/reset.html">Reset scroll</a>
27+
</body>
28+
</html>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="turbo-visit-scroll" content="reset">
6+
<title>Scroll Restoration</title>
7+
<script src="/dist/turbo.es2017-umd.js" data-turbo-track="reload"></script>
8+
<script src="/src/tests/fixtures/test.js"></script>
9+
<style>
10+
li {
11+
min-height: 50vh;
12+
}
13+
</style>
14+
</head>
15+
<body>
16+
<h1>Scroll Restoration: reset</h1>
17+
<ul>
18+
<li id="one">One</li>
19+
<li id="two">Two</li>
20+
<li id="three">Three</li>
21+
<li id="four">Four</li>
22+
<li id="five">Five</li>
23+
</ul>
24+
<a id="default" href="/src/tests/fixtures/scroll_restoration.html">Default scroll</a>
25+
<a id="preserve" href="/src/tests/fixtures/scroll_restoration/preserve.html">Preserve scroll</a>
26+
<a id="reset" href="/src/tests/fixtures/scroll_restoration/reset.html">Reset scroll</a>
27+
</body>
28+
</html>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { test, expect } from "@playwright/test"
2+
import { nextEventNamed } from "../helpers/page"
3+
4+
test("it resets the scroll position when the turbo-visit-scroll meta tag is 'reset'", async ({ page }) => {
5+
await page.goto("/src/tests/fixtures/scroll_restoration.html")
6+
7+
const reset = page.locator("#reset")
8+
await reset.scrollIntoViewIfNeeded()
9+
10+
await clickWithoutScrolling(reset)
11+
await nextEventNamed(page, "turbo:render")
12+
13+
expect.poll(async () => await scrollPosition(page)).toEqual([ 0, 0 ])
14+
})
15+
16+
test("it preserves the scroll position during 'restore' visits when the turbo-visit-scroll meta tag is 'reset'", async ({ page }) => {
17+
await page.goto("/src/tests/fixtures/scroll_restoration/reset.html")
18+
19+
const link = page.locator("#default")
20+
await link.scrollIntoViewIfNeeded()
21+
await clickWithoutScrolling(link)
22+
23+
await nextEventNamed(page, "turbo:render")
24+
await page.goBack()
25+
await nextEventNamed(page, "turbo:render")
26+
27+
expect.poll(async () => await scrollPosition(page)).not.toEqual([ 0, 0 ])
28+
})
29+
30+
test("it preserves the scroll position when the turbo-visit-scroll meta tag is 'preserve'", async ({ page }) => {
31+
await page.goto("/src/tests/fixtures/scroll_restoration.html")
32+
33+
const preserve = page.locator("#preserve")
34+
await preserve.scrollIntoViewIfNeeded()
35+
36+
const [ scrollTop, scrollLeft ] = await page.evaluate(() => [window.scrollY, window.scrollX])
37+
38+
await clickWithoutScrolling(preserve)
39+
await nextEventNamed(page, "turbo:render")
40+
41+
expect.poll(async () => await scrollPosition(page)).toEqual([ scrollTop, scrollLeft ])
42+
})
43+
44+
test("it does not preserve the scroll position during 'restore' visits when coming from a pack with turbo-visit-scroll meta tag of 'reset'", async ({ page }) => {
45+
await page.goto("/src/tests/fixtures/scroll_restoration/preserve.html")
46+
47+
const reset = page.locator("#reset")
48+
await reset.scrollIntoViewIfNeeded()
49+
await clickWithoutScrolling(reset)
50+
51+
await nextEventNamed(page, "turbo:render")
52+
await page.goBack()
53+
await nextEventNamed(page, "turbo:render")
54+
55+
expect.poll(async () => await scrollPosition(page)).not.toEqual([ 0, 0 ])
56+
})
57+
58+
function clickWithoutScrolling(locator) {
59+
// not using locator.click() because it can reset the scroll position
60+
return locator.evaluate((element) => element.click())
61+
}
62+
63+
function scrollPosition(page) {
64+
return page.evaluate(() => [
65+
document.documentElement.scrollTop || document.body.scrollTop,
66+
document.documentElement.scrollLeft || document.body.scrollLeft
67+
])
68+
}

0 commit comments

Comments
 (0)