diff --git a/src/index.ts b/src/index.ts index 7976ffa..dc9c86c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import type { RouteLocationNormalized, RouteLocationNormalizedLoaded, Router } f import type { NavigationType, RouterScrollBehaviorOptions, ScrollPositionCoordinates, ScrollPositionCoordinatesGroup } from './types' const STATE_KEY = 'vueRouterScroller' +const DEFAULT_INTERVAL = 200 /** * Setup router scroll behavior directly with a router instance. @@ -14,20 +15,40 @@ export function setupRouterScroller(router: Router, options: RouterScrollBehavio console.warn('`scrollBehavior` options in Vue Router is overwritten by `vue-router-scroller` plugin, you can remove it from createRouter()') router.options.scrollBehavior = () => {} + options.storeInterval = options.storeInterval ?? DEFAULT_INTERVAL const positionsMap = new Map() + let saveTimerId: number | null = null - // `beforeLeave` but after all other hooks - router.beforeResolve((to, from) => { - // `beforeResolve` is also called when going back in history, we ignores it - if (history.state?.current === to.fullPath) + function startTimer() { + if (saveTimerId !== null) return - const pos = capturePositions(options) - const key = getScrollKey(from.fullPath) - positionsMap.set(key, pos) - history.replaceState({ ...history.state, [STATE_KEY]: pos }, '') - }) + // Periodically save scroll positions + saveTimerId = window.setInterval(() => { + const pos = capturePositions(options) + const key = getScrollKey(history.state.fullPath) + positionsMap.set(key, pos) + history.replaceState({ ...history.state, [STATE_KEY]: pos }, '') + }, options.storeInterval) + } + + function stopTimer() { + if (saveTimerId === null) + return + + clearInterval(saveTimerId) + saveTimerId = null + } + + startTimer() + + // Stop saving scroll positions while the state is being manipulated by + // the browser/vue-router. Note that we can't listen to `popstate` because + // vue-router also listens to it, and its listener is setup first. Listening + // to `popstate` here would actually handle the event after all the callbacks + // which is too late + router.beforeEach(() => stopTimer()) router.afterEach((to, from, failure) => { if (isNavigationFailure(failure)) @@ -39,6 +60,9 @@ export function setupRouterScroller(router: Router, options: RouterScrollBehavio nextTick(() => { applyPositions(to, from, pos, type, options) + + // Safe to start storing again + startTimer() }) }) } diff --git a/src/types.ts b/src/types.ts index cea88a8..d0efcb8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -36,4 +36,10 @@ export interface RouterScrollBehaviorOptions { * Default scroll behavior applied, when not specified in the handler */ behavior?: ScrollOptions['behavior'] + + /** + * How often to check and store scroll positions, in milliseconds. Very low values + * can cause performance issues. Default is 200 + */ + storeInterval?: number }