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.
Related
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
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);
});
}
},
When I logged in the app and select edit some item, they make a $router.push to the edit view, the problem is that they render the component two times, I figured this out by doing a console.log on mounted(){}.. However, if I reload the page and click edit other time they make the render correctly, only one time.
This is the relevant code:
//listItemsView script
editItem(item) {
this.$router.push({ name: 'editPolicy', params:{policyTest: item}})
},
//editItemView script
export default {
props:{
policyTest:{
type: Object,
required: true,
}
mounted(){
console.log(this.policyTest);
console.log('entra');
},
}
//router script
{
path: '/editPolicy/',
name: 'editPolicy',
component: () => import('../views/policies/editPolicy.vue'),
props: true,
meta:{requireAuth:true}
}
router.beforeEach((to, from, next) => {
const user = auth.currentUser;
if(user !== null){
user.getIdTokenResult(true)
.then(function ({
claims
}) {
if (to.name === 'NewClient' && !claims.permissions.includes('Agregar Cliente')) {
next({name: 'notFoundPage'});
}else{
//In this case the router execute this next()
next()
}
})
} else {
if (to.matched.some(record => record.meta.requireAuth)) {
next({name: 'SignIn'});
} else {
next()
}
}
})
//html
<td class="text-left">
<v-icon small class="mr-2" #click="editItem(item)">fas fa-edit</v-icon>
</td>
i solved the problem, I'm use firebase authentication and in the main.js i'm detecting the user state change and for error i was creating two vue instances, one when start up the App and other when the user attemp to make their logging.. solved, thanks
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
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