vuejs route between components scroll same - vue.js

<template>
<div class="index">
<common-header id="common-header" class="common-header" v-el:commonheader></common-header>
<router-view transition keep-alive class="index-view"></router-view>
</div>
</template>
the route view will show two component A and B, while component A scrollTop is 0, I route to component B, and scroll down, and then route to component A, A is also scroll. Anyone have any ideas?

You can add a global before hook to the router, which will be called before every route transition starts and scroll to the top of the page. That is how I have solved it. Read here
Vue 1.
router.beforeEach(function (transition) {
window.scrollTo(0, 0)
transition.next()
})
For Vue2:
const router = new VueRouter({
scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
}
})
Refer here

For Vuejs2.0, there is a new accepted way dealing with scroll behavior on page changes:
import VueRouter from 'vue-router';
const router = new VueRouter({
scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
}
})
This will scroll to the top of the page after each navigation change. You can read up on the full, official documentation about this functionality here.

I think you are scroll the wrong element.I have make the same mistake like that:component A and component B is both in element Body,and I make the body scroll,so as long as you move the scroll bar,it will work for both A&B.I finally work out it by scrolling component instead of scrolling the body.

Related

How to use Vue Router scroll behavior with page transition

I'm using Vue Router 4 on an application where the top level RouterView is wrapped in a transition, like so:
<router-view v-slot="{ Component }">
<transition mode="out-in">
<component :is="Component" />
</PageTransition>
</router-view>
When I try to add scroll behavior to the Router, to scroll to a specific element when users navigate back to the index page, the scroll behavior fires during the out phase of the transition, when the index page isn't mounted yet, so the element isn't found.
eg.
const router = createRouter({
history: createWebHashHistory(),
routes,
scrollBehavior (to, from) {
if (to.name === 'index' && isContentPage(from)) {
return { el: '#menu' }
}
return undefined
}
})
I would get a warning in the console: Couldn't find element using selector "#menu" returned by scrollBehavior.
The Vue Router docs on scroll behavior mention that it's possible to work around this issue by hooking up to transition events:
It's possible to hook this up with events from a page-level transition component to make the scroll behavior play nicely with your page transitions, but due to the possible variance and complexity in use cases, we simply provide this primitive to enable specific userland implementations.
But I couldn't figure out what sort of approach it was suggesting, nor could I find any "userland implementations".
Finally I found a solution to this, which was to use a module holding some state—in this case a Promise—to act as the link between the transition and the router.
The module:
let transitionState: Promise<void> = Promise.resolve()
let resolveTransition: (() => void)|null = null
/**
* Call this before the leave phase of the transition
*/
export function transitionOut (): void {
transitionState = new Promise(resolve => {
resolveTransition = resolve
})
}
/**
* Call this in the enter phase of the transition
*/
export function transitionIn (): void {
if (resolveTransition != null) {
resolveTransition()
resolveTransition = null
}
}
/**
* Await this in scrollBehavior
*/
export function pageTransition (): Promise<void> {
return transitionState
}
I hooked up the transition events:
<router-view v-slot="{ Component }">
<transition mode="out-in" #before-leave="transitionOut" #enter="transitionIn">
<component :is="Component" />
</PageTransition>
</router-view>
...and in the Router:
const router = createRouter({
history: createWebHashHistory(),
routes,
async scrollBehavior (to, from) {
if (to.name === 'index' && isContentPage(from)) {
await pageTransition()
return { el: '#menu' }
}
return undefined
}
})
What's more, I actually have a nested RouterView also wrapped in a transition. With that transition extracted to a component, both instances could call transitionOut and transitionIn and it seems to work (though I haven't tested it much for race conditions).
If anyone has found simpler solutions though, I'd be interested to see them.

How to stop FOUC when leaving route with 'vue-flickity' carousel/slider?

I'm using the vue-flickity package for MetaFizzy's Flickity in my Vue app. When navigating away from a route that has an instance of vue-flickity slider, you get a brief FOUC showing all the slides unstyled in the document as the <Flickity /> slider is dismounted(?) and the view changes.
I've tried wrapping it in a <keep-alive>, but that doesn't help.
What would be the best approach to hiding or "animating out" the component before the user navigates away from a route?
I also tried to use beforeRouteLeave(), transition opacity on <Flickity ref="mySlider" /> to 0, then change route. I tried something like the following, but it didn't work as expected:
// SliderView.vue
<template>
<Flickity ref="mySlider">
<div v-for="(slide, index) in slides" :key="index">
// slide content markup
</div>
</Flickity>
</template>
<script>
import 'Flickity' from 'vue-flickity'
export default {
name: 'SliderView'
}
</script>
// router.js
import Vue from 'vue'
import Router from 'vue-router'
import SliderView from './views/SliderView.vue'
export default new Router({
routes: [
{
path: '/routeWithSlider',
component: SliderView,
beforeRouteLeave (to, from, next) {
const slider = this.$refs.mySlider
if (slider) {
slider.style.transition = 'opacity .5s'
slider.style.opacity = 0
setTimeout(() => {
next()
}, 600)
} else {
next()
}
}
}
]
})
Is this the correct approach, or should I be approaching this differently?
Also, if there was a way to do this globally for all <Flickity /> instances on all routes without specifying refs, that would also be useful too.
I haven't been using Vue for that long, so am looking for some guidance on the direction to take.
I found the best way to acheive this is to use BeforeRouteLeave() lifecycle hook on the component with the slider to animate the Flickity slider exit before changing route:
beforeRouteLeave (from, to, next) {
const slider = this.$refs.flickity.$el
slider.style.transition = 'opacity .25s ease'
slider.style.opacity = 0
setTimeout(() => {
next()
}, 250)
}

vue-router transitions with slide not scrolling to the top

I use transitions in vue-router and it slides nice, but I would like to see the coming page from the top, instead, I need to scroll the new page manually to the top and this is not ok.
So I added the option scroll-behavior which should help me:
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.
Here my configuration:
Router
const routers = [...]
const router = new VueRouter({
routes: routes,
mode: 'history',
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
//return savedPosition
return { x: 0, y: 0 } // just for debugging
} else {
return { x: 0, y: 0 }
}
}
});
App.vue
<template>
<div class="home">
<header-comp></header-comp>
<main>
<transition name="slide"
enter-active-class="animated slideInLeft faster"
leave-active-class="animated slideOutRight faster">
<router-view></router-view>
</transition>
</main>
<footer-comp></footer-comp>
</div>
</template>

Vue-router redirect to hash with timeout

I have route like this:
{
path: '/kontakt',
redirect: '#contact',
component: index
},
and scrollBehavior:
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
}
if(!to.hash) {
return {
x: 0, y: 0
}
}
if(to.hash) {
return {
selector: to.hash
}
}
}
and IMO because I display my page 1 second after user actually enter it (I show logo for 1sec) above code doesn't scroll page to #contact div when I enter /kontakt route.
app.vue
<div v-show="timeGap">
<router-view />
</div>
here timeGap is changed to true after 1sec in setTimeout method. How to fix so it scroll to #contact div after this timeout?
You have to move the control of your scrolling behaviour from your router scrollBehavior handler to your component index and there into the place that knows the logo show state is off.
This might be:
a Vue lifecycle hook (either mounted or updated);
or other point in this component that knows the logo state (watch after computed ?);
As soon as you find that place (watch) in your component you have to check if the logo state is off and do your scroll using this approach.

Vue-router component reusing

I would like to know how can I stop component reusing in Vue-router.
I'm building a simple page application and I am unable to update data after clicking the same link twice.
Is it possible to somehow force reloading or what are the best practices in my situation?
Use the key attribute on router-view set to current url. It's built in, so no need to write any code.
<router-view :key="$route.fullPath"></router-view>
Vue Router reuses the same component therefore the mounted hook won't be called. As stated in the documentation:
The same component instance will be reused [...] the lifecycle hooks of the component will not be called.
If you want to update the data you have two options:
Watch the $route object
const User = {
template: '...',
watch: {
'$route' (to, from) {
// react to route changes...
}
}
}
Use the beforeRouteUpdate navigation guard
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// react to route changes...
// don't forget to call next()
}
}
For a more detailed explanation you can check the section Reacting to Param Changes of the Vue Router documentation: https://router.vuejs.org/guide/essentials/dynamic-matching.html#reacting-to-params-changes.
One way to do this is to put a key on the router-view and append a timestamp querystring to your router-link
const Home = {
template: '<div>Home</div>',
created() {
console.log('This should log everytime you click home.');
},
};
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/', component: Home },
]
});
new Vue({
router,
el: '#app',
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<router-link :to="`/?q=${Date.now()}`">/home</router-link>
<router-view :key="$route.fullPath"></router-view>
</div>
One reason not to do it this way is because it'll force rerenders on components that you may want to be reused such as on routes like
/posts/1
/posts/2