@@ -2,6 +2,21 @@ import { onMount } from 'svelte';
2
2
import { normalize_path } from '../../utils/url' ;
3
3
import { get_base_uri } from './utils' ;
4
4
5
+ // We track the scroll position associated with each history entry in sessionStorage,
6
+ // rather than on history.state itself, because when navigation is driven by
7
+ // popstate it's too late to update the scroll position associated with the
8
+ // state we're navigating from
9
+ const SCROLL_KEY = 'sveltekit:scroll' ;
10
+
11
+ /** @typedef {{ x: number, y: number } } ScrollPosition */
12
+ /** @type {Record<number, ScrollPosition> } */
13
+ let scroll_positions = { } ;
14
+ try {
15
+ scroll_positions = JSON . parse ( sessionStorage [ SCROLL_KEY ] ) ;
16
+ } catch {
17
+ // do nothing
18
+ }
19
+
5
20
function scroll_state ( ) {
6
21
return {
7
22
x : pageXOffset ,
@@ -63,6 +78,11 @@ export class Router {
63
78
history . replaceState ( { ...history . state , 'sveltekit:index' : 0 } , '' , location . href ) ;
64
79
}
65
80
81
+ // if we reload the page, or Cmd-Shift-T back to it,
82
+ // recover scroll position
83
+ const scroll = scroll_positions [ this . current_history_index ] ;
84
+ if ( scroll ) scrollTo ( scroll . x , scroll . y ) ;
85
+
66
86
this . hash_navigating = false ;
67
87
68
88
this . callbacks = {
@@ -75,9 +95,7 @@ export class Router {
75
95
}
76
96
77
97
init_listeners ( ) {
78
- if ( 'scrollRestoration' in history ) {
79
- history . scrollRestoration = 'manual' ;
80
- }
98
+ history . scrollRestoration = 'manual' ;
81
99
82
100
// Adopted from Nuxt.js
83
101
// Reset scrollRestoration to auto when leaving page, allowing page reload
@@ -102,28 +120,16 @@ export class Router {
102
120
}
103
121
} ) ;
104
122
105
- // Setting scrollRestoration to manual again when returning to this page.
106
- addEventListener ( 'load' , ( ) => {
107
- history . scrollRestoration = 'manual' ;
108
- } ) ;
109
-
110
- // There's no API to capture the scroll location right before the user
111
- // hits the back/forward button, so we listen for scroll events
123
+ addEventListener ( 'visibilitychange' , ( ) => {
124
+ if ( document . visibilityState === 'hidden' ) {
125
+ this . #update_scroll_positions( ) ;
112
126
113
- /** @type {NodeJS.Timeout } */
114
- let scroll_timer ;
115
- addEventListener ( 'scroll' , ( ) => {
116
- clearTimeout ( scroll_timer ) ;
117
- scroll_timer = setTimeout ( ( ) => {
118
- // Store the scroll location in the history
119
- // This will persist even if we navigate away from the site and come back
120
- const new_state = {
121
- ...( history . state || { } ) ,
122
- 'sveltekit:scroll' : scroll_state ( )
123
- } ;
124
- history . replaceState ( new_state , document . title , window . location . href ) ;
125
- // iOS scroll event intervals happen between 30-150ms, sometimes around 200ms
126
- } , 200 ) ;
127
+ try {
128
+ sessionStorage [ SCROLL_KEY ] = JSON . stringify ( scroll_positions ) ;
129
+ } catch {
130
+ // do nothing
131
+ }
132
+ }
127
133
} ) ;
128
134
129
135
/** @param {Event } event */
@@ -196,6 +202,8 @@ export class Router {
196
202
// clicking a hash link and those triggered by popstate
197
203
this . hash_navigating = true ;
198
204
205
+ this . #update_scroll_positions( ) ;
206
+
199
207
const info = this . parse ( url ) ;
200
208
if ( info ) {
201
209
return this . renderer . update ( info , [ ] , false ) ;
@@ -225,7 +233,7 @@ export class Router {
225
233
226
234
this . _navigate ( {
227
235
url : new URL ( location . href ) ,
228
- scroll : event . state [ 'sveltekit:scroll' ] ,
236
+ scroll : scroll_positions [ event . state [ 'sveltekit:index' ] ] ,
229
237
keepfocus : false ,
230
238
chain : [ ] ,
231
239
details : null ,
@@ -254,6 +262,10 @@ export class Router {
254
262
} ) ;
255
263
}
256
264
265
+ #update_scroll_positions( ) {
266
+ scroll_positions [ this . current_history_index ] = scroll_state ( ) ;
267
+ }
268
+
257
269
/**
258
270
* Returns true if `url` has the same origin and basepath as the app
259
271
* @param {URL } url
@@ -401,6 +413,8 @@ export class Router {
401
413
} ) ;
402
414
}
403
415
416
+ this . #update_scroll_positions( ) ;
417
+
404
418
accepted ( ) ;
405
419
406
420
if ( ! this . navigating ) {
0 commit comments