add smooth behavior on scrollBehavior savedPosition in VueRouter - vue.js

Im adding the scrollBehavior in vueRouter and I can't find anywhere how can I add the behavior: smooth on the savedPosition in a timeout?
here is my code where I would like to add the smooth option :
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(savedPosition);
}, 1000);
});
}
},
Thank you alot in advance.

Try this
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return new Promise((resolve) => {
setTimeout(() => {
savedPosition.behavior = "smooth"
resolve(savedPosition);
}, 1000);
});
}
},

Related

Vue 3 scrollBehavior async scrolling

I'm trying to resolve a promise in the scrollBehavior function in vue 3. The vue router documentation is vague in regards to async scrolling (https://v3.router.vuejs.org/guide/advanced/scroll-behavior.html#async-scrolling).
With vue 2 I would have done something like this:
scrollBehavior: (to, from, savedPosition) => {
return new Promise((resolve) => {
const position = (function () {
if (savedPosition) {
return savedPosition;
}
})();
router.app.$root.$once("triggerScroll", () => {
router.app.$nextTick(() => resolve(position));
});
});
};
But since vue 3 removed the events api, this can no longer be done. How can I resolve the promise at the end of a page transition? I could use a library like tiny-emitter, but I would prefer to learn how to do this without.
In the router.js :
scrollBehavior: (to, from, savedPosition) => {
return new Promise((resolve) => {
const position = (function () {
if (savedPosition) {
return savedPosition;
}
})();
// Listen to an event here to resolve, but how ?
// resolve(position));
});
};
Transition afterEnter function:
function afterEnter(router, trans) {
// Do something here to trigger to resolve the promise
}
Thank you

NuxtLink to hash

I'm trying to use a NuxtLink to scroll to an anchor tag. From the docs I see I need to create this file app/router.scrollBehavior.js and place my code there.
For example, this works. It scrolls 500px in the y axis, but what I really want is to scroll to the hash.
export default function (to, from, savedPosition) {
if (to.hash) {
return { x: 0, y: 500 }
}
}
Events Page
<div
v-for="(event, i) in events"
:id="event.id"
:ref="event.id"
:key="i"
>
</div>
Navigation Component
<NuxtLink
v-for="item in items"
:key="`item.id"
:to="item.href"
>
{{ item.name }}
</NuxtLink>
I haven't been able to make it scroll to the hash. I tried several options an none of them seems to work. For example:
Does not work (I also tested with selector instead of el)
export default function (to, from, savedPosition) {
if (to.hash) {
return {
el: to.hash,
behavior: 'smooth',
}
}
}
Does not work
export default function (to, from, savedPosition) {
return new Promise((resolve, reject) => {
if (to.hash) {
setTimeout(() => {
resolve({
el: to.hash,
behavior: 'smooth',
})
}, 500)
}
})
}
Any ideas about what it could be the problem?
Ok, so finally this is what worked for me. I had to install Vue-Scroll and inside app/router.scrollBehavior.js
import Vue from 'vue'
import VueScrollTo from 'vue-scrollto'
Vue.use(VueScrollTo)
export default function (to, from, savedPosition) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (to.hash) {
VueScrollTo.scrollTo(to.hash, 300, { easing: 'linear', offset: -60 })
}
}, 500)
})
}
I used this answer as a reference How to smoothly scroll to an element via a hash in Nuxt? but I had to change the setTimeOut time from 10ms to 500ms because it wasn't working properly on my static page.

Router async beforeEach triggers two routes

I'm trying to wait for an asynchronous response in my route guard. The problem is that two routes are hit.
When the page is loaded, the / route is triggered instantly, followed by my target route
router.beforeEach(async (to, from, next) => {
await new Promise(resolve => {
setTimeout(resolve, 500)
})
next()
})
or
router.beforeEach((to, from, next) => {
setTimeout(next, 500)
})
})
In my log I see two entries when visiting /foo, first / then /foo after 500ms. I'm expecting to see only the latter:
setup(props, { root }) {
const hideNav = computed(() => {
console.log(root.$route.path)
return root.$route.meta?.hideNav
})
return {
hideNav
}
}
})
I'm using vue#2.6.12 and vue-router#3.4.9
I'm not sure but this how I guard my route in a Vue component hope this helps you
beforeRouteEnter(to, from, next) {
if (store.getters.isAuthenticated) {
Promise.all([
store.dispatch(FETCH_ARTICLE, to.params.slug),
store.dispatch(FETCH_COMMENTS, to.params.slug)
]).then(() => {
next();
});
} else {
Promise.all([store.dispatch(FETCH_ARTICLE, to.params.slug)]).then(() => {
next();
});
}
},
Have you tried using beforeEnter?
This way you can specify which routes are going to execute your function
Like this code below:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})

Anchor in Nuxt component not working on same page the anchor is included on

In my Footer Component I have this to link to the owners bio on the about page
<nuxt-link :to="{path: '/about', hash: 'alex'}">Alex</nuxt-link>
In the about/index.vue file I have the anchor
<hr class="my-5" id="alex" />
<h2 style>
Alex
<br />
<span style="font-size: 16px; font-weight: bold;">Co-Founder and Partner</span>
</h2>
On all pages this works when you click the link in the footer. It does not work if you are on the about page and click the footer link.
What can I do to make this also work on the about page?
Update Nuxt Link as below
<nuxt-link :to="{path: '/about', hash: '#alex'}">Alex</nuxt-link>
++ Updated
Need to add scroll behavior in nuxt.config.js as below
router: {
scrollBehavior: async function(to, from, savedPosition) {
if (savedPosition) {
return savedPosition;
}
const findEl = async (hash, x = 0) => {
return (
document.querySelector(hash) ||
new Promise(resolve => {
if (x > 50) {
return resolve(document.querySelector("#app"));
}
setTimeout(() => {
resolve(findEl(hash, ++x || 1));
}, 100);
})
);
};
if (to.hash) {
let el = await findEl(to.hash);
if ("scrollBehavior" in document.documentElement.style) {
return window.scrollTo({ top: el.offsetTop, behavior: "smooth" });
} else {
return window.scrollTo(0, el.offsetTop);
}
}
return { x: 0, y: 0 };
}
},
Codesandbox Link
You can use vue-scrollto package also and if you are using Vuetify with Nuxtjs than there is $vuetify.goTo available.
Just wanted to add, if people are stuck, you can add a file called router.scrollBehavior.js in your nuxt project:
https://nuxtjs.org/docs/2.x/configuration-glossary/configuration-router#scrollbehavior
Unfortunately, there are issues with it firing before the render - you can use nuxt/vue tick if you correctly import - but this still seems to work ( for anchors and for saved positions ):
export default async function (to, from, savedPosition) {
if (savedPosition) {
console.log("SAVED POSITION");
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
selector: savedPosition
});
}, 600);
});
// not working consistently due to render
//return savedPosition;
}
else if (to.hash) {
console.log("HASH");
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
selector: to.hash
});
}, 600);
});
}
else {
console.log("NORMAL");
return { x: 0, y: 0 };
}}
I am using Nuxt 3 and having the same problem, I solved the problem using this with thoughts of helping Nuxt 3 devs with same problem
Anchor tag with NuxtLink
<NuxtLink :to="{path: '/about', hash: '#support'}">Support</NuxtLink>
On the Nuxt page <script setup>
// Make sure you are in the client
// browser to access `document` variable
if (process.client) {
// get current route
const {currentRoute : route} = useRouter();
// make sure that hash is defined
if (route.value?.hash) {
// set hash
const hash = route.value?.hash
// use onMounted hook to run the code inside
// after the page is mounted to the DOM
onMounted(() => {
// get target element
let el = document.querySelector(hash)
// make sure that the element exists then scroll to that element
if(el) {
if ('scrollBehavior' in document.documentElement.style) {
window.scrollTo({ top: el.getBoundingClientRect().top+window.scrollY, behavior: 'smooth' })
} else {
window.scrollTo(0, el.getBoundingClientRect().top+window.scrollY)
}
}
})
}
}
If your are on Vue 3, you will have to create a file here app/router.options.ts with the following code inside
import type { RouterConfig } from '#nuxt/schema'
// https://router.vuejs.org/api/#routeroptions
export default <RouterConfig>{
scrollBehavior(to, from, savedPosition) {
return { el: to.hash }
}
}
Your nuxt link should look like this
<NuxtLink :to="{ path: '/', hash: '#projects' }">Projects</NuxtLink>
Checkout more more

Vue.js scroll to top of new page route after setTimeout

I have a page transition that doesn't work nicely when the scroll to the top of a new route is instant. I'd like to wait 100ms before it automatically scrolls to the top. The following code doesn't end up scrolling at all. Is there a way to do this?
export default new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'Home',
component: Home
}
],
scrollBehavior (to, from, savedPosition) {
setTimeout(() => {
return { x: 0, y: 0 }
}, 100);
}
})
This is natively supported by Vue now, use scrollBehaviour, like this:
export default new Router({
scrollBehavior() {
return { x: 0, y: 0 };
},
routes: [
{
path: '/',
name: 'Home',
component: Home
}
],
mode: 'history'
});
More here.
The other answers fail to handle edge cases such as:
Saved
Position - The saved position occurs when the user clicks the back or forward positions. We want to maintain the location the user was looking at.
Hash Links - E.g. http://example.com/foo#bar should navigate to the element on the page with an id of bar.
Finally, in all other cases we can navigate to the top of the page.
Here is the sample code that handles all of the above:
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes,
scrollBehavior: (to, from, savedPosition) => {
if (savedPosition) {
return savedPosition;
} else if (to.hash) {
return {
selector: to.hash
};
} else {
return { x: 0, y: 0 };
}
}
});
If you want this to happen on every route, you can do so in the before hook in the router:
const router = new VueRouter({ ... })
router.beforeEach(function (to, from, next) {
setTimeout(() => {
window.scrollTo(0, 0);
}, 100);
next();
});
If you are on an older version of vue-router, use:
router.beforeEach(function (transition) {
setTimeout(() => {
window.scrollTo(0, 0);
}, 100);
transition.next();
});
If you want to wait a long time use Async Scrolling of scrollBehaviour, like this:
export default new Router({
scrollBehavior() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ x: 0, y: 0 })
}, 100)
})
},
routes: [
{
path: '/',
name: 'Home',
component: Home
}
],
mode: 'history'
});
More here.
This is probably not the best way, but adding
document.body.scrollTop = document.documentElement.scrollTop = 0;
in a route's core component's (in this case, Home) mounted() function achieves what I want.
When using client-side routing, we may want to scroll to top when navigating to a new route, or preserve the scrolling position of history entries just like real page reload does. vue-router allows you to achieve these and even better, allows you to completely customize the scroll behavior on route navigation.
Note: this feature only works if the browser supports history.pushState.
scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
}
With Saved Position:
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}
For more information