--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <meta http-equiv="X-UA-Compatible" content="ie=edge" />
+ <title>Vue Router e2e tests - Scroll Behavior</title>
+ <!-- TODO: replace with local imports for promises and anything else needed -->
+ <script src="https://polyfill.io/v3/polyfill.min.js?features=default%2Ces2015"></script>
+
+ <style>
+ .fade-enter-active,
+ .fade-leave-active {
+ transition: opacity 0.5s ease;
+ }
+ .fade-enter,
+ .fade-leave-active {
+ opacity: 0;
+ }
+ .view {
+ border: 1px solid red;
+ height: 4500px;
+ position: relative;
+ }
+ </style>
+ </head>
+ <body>
+ <a href="/"><< Back to Homepage</a>
+ <hr />
+
+ <div id="app">
+ <h1>Scroll Behavior</h1>
+ <ul>
+ <li><router-link to="/">/</router-link></li>
+ <li><router-link to="/foo">/foo</router-link></li>
+ <li><router-link to="/bar">/bar</router-link></li>
+ <li><router-link to="/bar#anchor">/bar#anchor</router-link></li>
+ <li><router-link to="/bar#anchor2">/bar#anchor2</router-link></li>
+ <li><router-link to="/bar#1number">/bar#1number</router-link></li>
+ </ul>
+ <transition
+ name="fade"
+ mode="out-in"
+ @before-enter="flushWaiter"
+ @before-leave="setupWaiter"
+ >
+ <router-view class="view"></router-view>
+ </transition>
+ </div>
+ </body>
+</html>
--- /dev/null
+import { createRouter, createWebHistory, ScrollBehavior } from '../../src'
+import { RouteComponent } from '../../src/types'
+import { createApp } from 'vue'
+import { scrollWaiter } from './scrollWaiter'
+
+const Home: RouteComponent = { template: '<div class="home">home</div>' }
+const Foo: RouteComponent = { template: '<div class="foo">foo</div>' }
+const Bar: RouteComponent = {
+ template: `
+ <div class="bar">
+ bar
+ <div style="height:1500px"></div>
+ <p id="anchor" style="height:500px">Anchor</p>
+ <p id="anchor2" style="height:500px">Anchor2</p>
+ <p id="1number">with number</p>
+ </div>
+ `,
+}
+
+// scrollBehavior:
+// - only available in html5 history mode
+// - defaults to no scroll behavior
+// - return false to prevent scroll
+const scrollBehavior: ScrollBehavior = async function(to, from, savedPosition) {
+ await scrollWaiter.promise
+
+ if (savedPosition) {
+ // savedPosition is only available for popstate navigations.
+ return savedPosition
+ } else {
+ let position: ReturnType<ScrollBehavior>
+
+ // scroll to anchor by returning the selector
+ if (to.hash) {
+ position = { selector: to.hash }
+
+ // specify offset of the element
+ if (to.hash === '#anchor2') {
+ // TODO: allow partial { y: 100 }
+ position.offset = { x: 0, y: 100 }
+ }
+
+ // bypass #1number check
+ if (/^#\d/.test(to.hash) || document.querySelector(to.hash)) {
+ return position
+ }
+
+ // if the returned position is falsy or an empty object,
+ // will retain current scroll position.
+ return false
+ }
+
+ // check if any matched route config has meta that requires scrolling to top
+ 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 false
+ }
+}
+
+const webHistory = createWebHistory('/' + __dirname)
+const router = createRouter({
+ history: webHistory,
+ scrollBehavior,
+ routes: [
+ { path: '/', component: Home, meta: { scrollToTop: true } },
+ { path: '/foo', component: Foo },
+ { path: '/bar', component: Bar, meta: { scrollToTop: true } },
+ ],
+})
+const app = createApp({
+ setup() {
+ return {
+ flushWaiter: scrollWaiter.flush,
+ setupWaiter: scrollWaiter.add,
+ }
+ },
+})
+app.use(router)
+
+window.vm = app.mount('#app')
// resolve, reject arguments of Promise constructor
type OnReadyCallback = [() => void, (reason?: any) => void]
-interface ScrollBehavior {
+export interface ScrollBehavior {
(
to: RouteLocationNormalized,
from: RouteLocationNormalizedLoaded,
savedPosition: ScrollToPosition | null
- ): ScrollPosition | Promise<ScrollPosition>
+ ): // TODO: implement false nad refactor promise based type
+ | ScrollPosition
+ | Promise<ScrollPosition | false | undefined>
+ | false
+ | undefined
}
export interface RouterOptions {
await nextTick()
const position = await scrollBehavior(to, from, scrollPosition || null)
console.log('scrolling to', position)
- scrollToPosition(position)
+ position && scrollToPosition(position)
}
const router: Router = {