From 12e92094df46129ddf75d0fa8e3d9816644200de Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 27 May 2020 14:52:16 +0200 Subject: [PATCH] feat(scroll): allow passing behavior option BREAKING CHANGE: `scrollBehavior` doesn't accept an object with `x` and `y` coordinates anymore. Instead it accepts an object like [`ScrollToOptions`](https://developer.mozilla.org/en-US/docs/Web/API/ScrollToOptions) with `left` and `top` properties. You can now also pass the [`behavior`](https://developer.mozilla.org/en-US/docs/Web/API/ScrollToOptions/behavior) property to enable smooth scrolling in most browsers. --- README.md | 1 + e2e/scroll-behavior/index.ts | 4 +-- playground/router.ts | 2 +- src/history/html5.ts | 4 +-- src/router.ts | 6 ++-- src/scrollBehavior.ts | 66 +++++++++++++++++++++++------------- 6 files changed, 51 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 765c25a4b..0e0d2be0b 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ Check the [playground](https://github.com/vuejs/vue-router-next/tree/master/play // resolve the request }) ``` +- The object returned in `scrollBehavior` is now similar to [`ScrollToOptions`](https://developer.mozilla.org/en-US/docs/Web/API/ScrollToOptions): `x` is renamed to `left` and `y` is renamed to `top`. ### Typings diff --git a/e2e/scroll-behavior/index.ts b/e2e/scroll-behavior/index.ts index f98dbea82..280c701ea 100644 --- a/e2e/scroll-behavior/index.ts +++ b/e2e/scroll-behavior/index.ts @@ -40,7 +40,7 @@ const scrollBehavior: ScrollBehavior = async function ( // specify offset of the element if (to.hash === '#anchor2') { - position.offset = { y: 100 } + position.offset = { top: 100 } } if (document.querySelector(position.selector)) { @@ -56,7 +56,7 @@ const scrollBehavior: ScrollBehavior = async function ( if (to.matched.some(m => m.meta.scrollToTop)) { // coords will be used if no selector is provided, // or if the selector didn't match any element. - return { x: 0, y: 0 } + return { left: 0, top: 0 } } return false diff --git a/playground/router.ts b/playground/router.ts index 2b030a548..924dccb19 100644 --- a/playground/router.ts +++ b/playground/router.ts @@ -152,7 +152,7 @@ export const router = createRouter({ } else { // TODO: check if parent in common that works with alias if (to.matched.every((record, i) => from.matched[i] !== record)) - return { x: 0, y: 0 } + return { left: 0, top: 0 } } // leave scroll as it is by not returning anything // https://github.com/Microsoft/TypeScript/issues/18319 diff --git a/src/history/html5.ts b/src/history/html5.ts index 4276caaa0..863cca601 100644 --- a/src/history/html5.ts +++ b/src/history/html5.ts @@ -12,7 +12,7 @@ import { } from './common' import { computeScrollPosition, - ScrollPositionCoordinates, + _ScrollPositionNormalized, } from '../scrollBehavior' import { warn } from '../warning' import { stripBase } from '../location' @@ -27,7 +27,7 @@ interface StateEntry extends HistoryState { forward: HistoryLocationNormalized | null position: number replaced: boolean - scroll: Required | null | false + scroll: _ScrollPositionNormalized | null | false } /** diff --git a/src/router.ts b/src/router.ts index 48f0a9c42..41aede494 100644 --- a/src/router.ts +++ b/src/router.ts @@ -15,13 +15,13 @@ import { } from './types' import { RouterHistory, HistoryState } from './history/common' import { - ScrollPositionCoordinates, ScrollPosition, getSavedScrollPosition, getScrollKey, saveScrollPosition, computeScrollPosition, scrollToPosition, + _ScrollPositionNormalized, } from './scrollBehavior' import { createRouterMatcher, PathParserOptions } from './matcher' import { @@ -59,7 +59,7 @@ export interface ScrollBehavior { ( to: RouteLocationNormalized, from: RouteLocationNormalizedLoaded, - savedPosition: Required | null + savedPosition: _ScrollPositionNormalized | null ): Awaitable } @@ -773,7 +773,7 @@ export function createRouter(options: RouterOptions): Router { ): Promise { if (!isBrowser || !scrollBehavior) return Promise.resolve() - let scrollPosition: Required | null = + let scrollPosition: _ScrollPositionNormalized | null = (!isPush && getSavedScrollPosition(getScrollKey(to.fullPath, 0))) || ((isFirstNavigation || !isPush) && (history.state as HistoryState) && diff --git a/src/scrollBehavior.ts b/src/scrollBehavior.ts index 322543a6a..8417480ca 100644 --- a/src/scrollBehavior.ts +++ b/src/scrollBehavior.ts @@ -1,15 +1,33 @@ -import { RouteLocationNormalized, RouteLocationNormalizedLoaded } from './types' +import { + RouteLocationNormalized, + RouteLocationNormalizedLoaded, + _RouteLocationBase, +} from './types' import { warn } from './warning' +// we use types instead of interfaces to make it work with HistoryStateValue type + +/** + * Scroll position similar to + * {@link https://developer.mozilla.org/en-US/docs/Web/API/ScrollToOptions | `ScrollToOptions`}. + * Note that not all browsers support `behavior`. + */ export type ScrollPositionCoordinates = { - /** - * x position. 0 if not provided - */ - x?: number - /** - * y position. 0 if not provided - */ - y?: number + behavior?: ScrollOptions['behavior'] + left?: number + top?: number +} + +/** + * Internal normalized version of {@link ScrollPositionCoordinates} that always + * has `left` and `top` coordinates. + * + * @internal + */ +export type _ScrollPositionNormalized = { + behavior?: ScrollOptions['behavior'] + left: number + top: number } export interface ScrollPositionElement { @@ -47,24 +65,25 @@ export interface ScrollBehaviorHandler { function getElementPosition( el: Element, offset: ScrollPositionCoordinates -): Required { +): _ScrollPositionNormalized { const docRect = document.documentElement.getBoundingClientRect() const elRect = el.getBoundingClientRect() return { - x: elRect.left - docRect.left - (offset.x || 0), - y: elRect.top - docRect.top - (offset.y || 0), + behavior: offset.behavior, + left: elRect.left - docRect.left - (offset.left || 0), + top: elRect.top - docRect.top - (offset.top || 0), } } export const computeScrollPosition = () => ({ - x: window.pageXOffset, - y: window.pageYOffset, - } as Required) + left: window.pageXOffset, + top: window.pageYOffset, + } as _ScrollPositionNormalized) export function scrollToPosition(position: ScrollPosition): void { - let normalizedPosition: ScrollPositionCoordinates + let scrollToOptions: ScrollPositionCoordinates if ('selector' in position) { /** @@ -105,12 +124,14 @@ export function scrollToPosition(position: ScrollPosition): void { warn(`Couldn't find element with selector "${position.selector}"`) return } - normalizedPosition = getElementPosition(el, position.offset || {}) + scrollToOptions = getElementPosition(el, position.offset || {}) } else { - normalizedPosition = position + scrollToOptions = position } - window.scrollTo(normalizedPosition.x || 0, normalizedPosition.y || 0) + if ('scrollBehavior' in document.documentElement.style) + window.scrollTo(scrollToOptions) + else window.scrollTo(scrollToOptions.left || 0, scrollToOptions.top || 0) } export function getScrollKey(path: string, delta: number): string { @@ -118,14 +139,11 @@ export function getScrollKey(path: string, delta: number): string { return position + path } -export const scrollPositions = new Map< - string, - Required ->() +export const scrollPositions = new Map() export function saveScrollPosition( key: string, - scrollPosition: Required + scrollPosition: _ScrollPositionNormalized ) { scrollPositions.set(key, scrollPosition) }