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

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

Related

Vue router - path #ID scrollBehavior

I have created a MENU where I link via <router-link> but certain links are linked to the same page using (anchors).
When I'm on the Work page and I click on the #services section, which is on the Bio page, the section is displayed correctly, but if I want to go to the services section on the Bio page, the URL just changes, but it won't go to the right section for me.
noubtest.com
NAVIGATION
<router-link v-show="!mobile" class="link bio" :to="{ name: 'Home' }">Bio</router-link>
<router-link v-show="!mobile" class="link link2" :to="{ name: 'Services' }">Services</router-link>
<router-link v-show="!mobile" class="link link2" :to="{ name: 'SelectedWork' }">Work</router-link>
ROUTER
{
path: "/home",
name: "Home",
component: Home,
meta: {
title: "Bio",
requiresAuth: false,
},
},
{
path: "/home#fifthPage",
name: "Services",
component: Home,
meta: {
title: "Services",
requiresAuth: false,
},
},
const router = new VueRouter({
mode: "history",
routes,
scrollBehavior() {
return { x: 0, y: 0 };
},
});
router.beforeEach((to, from, next) => {
document.title = `${to.meta.title} | YounesFilm`;
next();
});
router.beforeEach(async (to, from, next) => {
let user = firebase.auth().currentUser;
let admin = null;
if (user) {
let token = await user.getIdTokenResult();
admin = token.claims.admin;
}
if (to.matched.some((res) => res.meta.requiresAuth)) {
if (user) {
if (to.matched.some((res) => res.meta.requiresAdmin)) {
if (admin) {
return next();
}
return next({ name: "Home" });
}
return next();
}
return next({ name: "Home" });
}
return next();
});
export default router;
How can I click through the page between sections?
You must switch your VueRouter from hash mode to history mode of routing - then hashtags will work but in a different way.
Your routes should not have a hash symbol # inside their path - instead, you should provide it under the hash attribute of the route link:
<router-link :to="{ name: pathName, hash: '#text' }">
Jump to content
</router-link>
But this alone is not enough. You also need to alter the scrollBehavior of the VueRouter:
import { routes } from './routes.js';
const router = new VueRouter({
routes,
scrollBehavior(to, from, savedPosition)
{
if (savedPosition)
{
return savedPosition;
}
if (to.hash)
{
return { selector: to.hash }; // <==== the important part
}
return { x: 0, y: 0 };
}
});
With a few research, I found two things that could help you.
First, this error is known and discussed on github vue-router issues page.
Second, I found that Workaround on npmjs.com, and you could probably give it a try.
EDIT
I found another solution to a similar problem here.
And from that page, I found a scrollBehavior example like this:
scrollBehavior: function (to) {
if (to.hash) {
return {
selector: to.hash
}
}
}
And if it still doesn't work, you could try to use
:to="{ name: 'Home', hash: 'fifthPage'}".

Infinite loop for vue-router's beforeEach method

I want to replace one of the params of to if to's name is a specific one and I have no idea why this doesn't work.
export const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
scrollBehavior() {
return { x: 0, y: 0 };
},
routes: [
{
name: 'Details Page',
path: 'details/:name/:id',
component: () => import('#/views/Details.vue'),
meta: {
encode: true,
},
},
// other routes... (none of them has encode = true)
]
});
function needsEncode(route) {
return route.meta && route.meta.encode;
}
router.beforeEach(async (to, from, next) => {
if (to.name === 'Details Page' && needsEncode(to)) {
const toEncoded = Object.assign({}, to, {
meta: {
encode: false,
},
params: {
name: encodeURIComponent(to.params.name),
id: to.params.id,
},
});
return next(toEncoded);
}
return next();
}
What am I doing wrong?
Please don't store state in the meta property. Instead you can check if the name is already encoded:
function needsEncode(route) {
return route.meta && route.meta.encode && ( // <-- changes start here
decodeURIComponent(to.params.name) === to.params.name
) // <-- changes ends here
}
router.beforeEach(async (to, from, next) => {
if (to.name === 'Details Page' && needsEncode(to)) {
const toEncoded = Object.assign({}, to, {
// remove meta
//meta: {
// encode: false,
//},
params: {
name: encodeURIComponent(to.params.name),
id: to.params.id,
},
});
return next(toEncoded);
}
return next();
}
If decodeURIComponent(to.params.name) is equal to the name, then it's not encoded yet and you can proceed to encode it.

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

VueJS scroll to section from different route

I am trying to scroll to an anchor on a page using Vue and Vue Router (with history mode).
When on the index page, the scroll behaviour works as expected by jumping to the section.
However, when I am another page, it loads the index page at the top and not where the anchor is pointing to.
I’m sure it’s a very simple thing but can’t get my head round it!
Any help is appreciated!
My router index:
export default new Router({
scrollBehavior: function(to, from, savedPosition) {
if (to.hash) {
return {selector: to.hash}
} else {
return {x: 0, y: 0}
}
},
mode: 'history',
routes: [ ... ]
})
My Navigation:
<router-link #click.native="closeNav" to="/#enter">Enter</router-link>
<router-link #click.native="closeNav" to="/#prizes">Prizes</router-link>
<router-link #click.native="closeNav" to="/#faqs">FAQ</router-link>
<router-link #click.native="closeNav" to="/contactus">Contact</router-link>
Vue Router v3.x
This is a bit of an old question and OP has almost surely found a solution already, but for anyone running into this problem, this should do the trick:
<router-link :to="{ name: 'Homepage', hash: '#enter' }">Enter</router-link>
<router-link :to="{ name: 'Homepage', hash: '#prizes' }">Prizes</router-link>
<router-link :to="{ name: 'Homepage', hash: '#faqs' }">FAQ</router-link>
<router-link :to="{ name: 'Contact' }">Contact</router-link>
This should allow you to have these links accessible from other views/components, and when clicked will redirect you to the named route (Homepage in this case), and scroll to the hash specified (#enter, #prizes, #faqs).
In addition to the router code snippet in the question, you can add smooth scrolling using the native window.scrollTo method:
export default new Router({
routes: [],
mode: 'history',
scrollBehavior (to, from, savedPosition) {
if (to.hash) {
return window.scrollTo({
top: document.querySelector(to.hash).offsetTop,
behavior: 'smooth'
})
} else {
return { x: 0, y: 0 }
}
}
})
Update for Vue Router v4.x
You write your router-links the same, but you can write the scroll behavior and element selection a bit neater now. From the docs
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return {
el: to.hash,
behavior: 'smooth'
}
}
}
})
I am using scrollIntoView() instead of window.scrollTo()
export default new Router({
routes: [],
mode: 'history',
scrollBehavior (to, from, savedPosition) {
if (to.hash) {
return document.querySelector(to.hash).scrollIntoView({ behavior: 'smooth' });
} else {
return savedPosition || { x: 0, y: 0 }
}
}
})

Vue.js scroll to top of page for same route

I can set scrolling behaviour to Vue.js Router like this:
const router = new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'index',
component: Main
},
{
path: '/some-path',
name: 'some-path',
component: SomePath
}
],
scrollBehavior() {
return {x: 0, y: 0}
}
})
This works perfectly when you click on the link with some page which is not current. When I click on the link which is already rendered, i.e. in the footer, nothing happens. Vue Router assumes there is no state transition. What is the preferred way to scroll up in this case?
You can't do this through vue-router, but you can add a scroll-to-top method to every router-link.
Just create a method like this:
methods: {
scrollToTop() {
window.scrollTo(0,0);
}
}
Add it to the link:
<router-link #click.native="$scrollToTop">
If you want to use it outside of your footer too, it's better to add it to the Vue prototype
Vue.prototype.$scrollToTop = () => window.scrollTo(0,0)
It's not a 100% solution but it's the simplest one
I couldn't get any of the above solutions working, and it was really frustrating.
What ended up working for me was the below:
const router = new Router({
mode: 'history',
routes: [...],
scrollBehavior() {
document.getElementById('app').scrollIntoView({ behavior: 'smooth' });
}
})
I mount my VueJs app to #app so I can be certain it is present and is available for selection.
You could make use of behavior: smooth:
moveTo () {
let to = this.moveToDown
? this.$refs.description.offsetTop - 60
: 0
window.scroll({
top: to,
left: 0,
behavior: 'smooth'
})
this.moveToDown = !this.moveToDown
}
The best solution I've found for this is: https://router.vuejs.org/guide/advanced/scroll-behavior.html
Specifically:
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
}
})
Expanding on the answer from Vitaly Migunov, you can instead add directly from the router a scrollTo method to the window object. This way you won't need to add the function to every router link.
const router = new Router({
mode: 'history',
routes: [...],
scrollBehavior() {
window.scrollTo(0,0);
}
})
Use refs for scroll to certain section
<template>
<body>
<div ref="section">
// Your content section
</div>
</body>
</template>
export default class MyPage extends Vue {
$refs!: {
section: HTMLFormElement;
};
scrollToTop() {
this.$refs.section.scrollTo(0, 0);
}
}
This worked for me:
const router = createRouter({
history: createWebHashHistory(),
routes,
scrollBehavior() {
document.getElementById('app').scrollIntoView({behavior:'smooth'});
}
})
Basically, I think you want to scroll to the top of the page, unless an internal page location is specified (e.g. www.example.com/home#blah). All of the answers so far would ignore this location parameter, and just scroll to the top anyway.
scrollBehavior(to, from, savedPosition) {
//If there is no hash parameter when we change vue, scroll to top of page
if (!to.hash) {
return { x: 0, y: 0 }
}
}
We can check if there is a location parameter using to.hash, then only scroll to the top if no hash location is present.
for vue3
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
scrollBehavior(to, from, savedPosition) {
// always scroll to top
return { top: 0 }
},
})
For Vue3 you should use scrollBehavior. Use left and top instead of x and y.
scrollBehavior(to, from, savedPosition) {
return { left: 0, top: 0, behavior: "smooth" };
}
I've tried all of the above answers and none did work for me; However I've tried this one and it did the job for me; Add this to your App.vue file
updated() {
this.$refs.main.scrollTo(0, 0)
},
Vue Js have inbuilt support for scrolling if the browser supports history.pushState.
It is very easy to configure, Just provide the scrollBehavior function, when creating Vue router instance like below:
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// page scroll to top for all route navigations
return { x: 0, y: 0 }
}
})
For more options and detail about Vue Scroll Behavior click here
Use this nice component: https://www.npmjs.com/package/vue-backtotop
<back-to-top text="Back to top"></back-to-top>
Alternatively, this also worked for me:
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
scrollBehavior(to, from, savedPosition) {
document.getElementById('app').scrollTop = 0
},
routes
})