Summary I am looking for a solution to create an animation across two different router views in Vue. The problem I met is that when the browser change to another router view, it will re-render the page. So the animation will disappear when the browser re-render.
The Animation
Basically, the app has two different pages, homepage and aboutpage. When the user clicks the blue menu button, a green navigation menu will slide from the left to right until covers the whole screen. This menu has two links: about and home. If the user clicks the about, a white overlay will move from the left and cover the whole screen, then it moves to the right and then disappears. The about page shows after it disappears.
Here is the codepen example that demostrate the animation effect I want to achieve (this demo did not use a router).
https://codepen.io/wl1664209734/pen/qBamXEL
Below is the code to recreate the animation in vue template in codepen
<template>
<div class="wrap">
<!--Transition Overlay -->
<transition
name="page-shift"
enter-active-class="animate__animated animate__slideInLeft animate__faster"
leave-active-class="animate__animated animate__slideOutRight animate__faster"
>
<div v-if="showOverlay" class="overlay"></div>
</transition>
<!--Navigation Menu -->
<transition
name="slide"
enter-active-class="animate__animated animate__slideInLeft animate__faster"
leave-active-class="animate__animated animate__slideOutLeft animate__faster"
>
<div v-if="menuOpen" class="navigation">
<div
class="container h-100 d-flex justify-content-center align-items-center"
>
<div class="text-center">
<h3 class="font-weight-bold text-light" @click="pageShiftToHome">
HOME
</h3>
<h3 class="font-weight-bold text-light" @click="pageShiftToAbout">
ABOUT
</h3>
</div>
</div>
</div>
</transition>
<!--NavBar -->
<div class="navbar d-flex justify-content-end">
<div
@click="menuOpen = !menuOpen"
class="nav-link font-weight-bold mt-3 btn btn-primary"
>
Menu
</div>
</div>
<!--Home Page -->
<div
v-if="showHomePage"
class="homepage d-flex justify-content-center align-items-center"
>
<h1 class="text-light">HOME</h1>
</div>
<!--About Page -->
<div
v-if="showAboutPage"
class="aboutpage d-flex justify-content-center align-items-center"
>
<h1 class="text-light">ABOUT</h1>
</div>
</div>
</template>
<script>
export default {
data() {
return {
menuOpen: false,
showHomePage: true,
showAboutPage: false,
showOverlay: false
};
},
methods: {
pageShiftToHome: function () {
this.showOverlay = !this.showOverlay;
const _this = this;
setTimeout(function () {
_this.menuOpen = !_this.menuOpen;
_this.showAboutPage = false;
_this.showHomePage = true;
}, 500);
setTimeout(function () {
_this.showOverlay = !_this.showOverlay;
}, 1000);
},
pageShiftToAbout: function () {
this.showOverlay = !this.showOverlay;
const _this = this;
setTimeout(function () {
_this.menuOpen = !_this.menuOpen;
_this.showHomePage = false;
_this.showAboutPage = true;
}, 500);
setTimeout(function () {
_this.showOverlay = !_this.showOverlay;
}, 1000);
}
}
};
</script>
<!-- Use preprocessors via the lang attribute! e.g. <style lang="scss"> -->
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
background: #292929;
}
.wrap {
width: 100%;
height: 100vh;
background: #292929;
.overlay {
width: inherit;
height: inherit;
background: #fff;
position: absolute;
z-index: 999;
}
.navigation {
width: inherit;
height: inherit;
background: #3ecc28;
position: absolute;
z-index: 111;
h3 {
cursor: pointer;
}
}
.navbar {
width: 100%;
z-index: 333;
.nav-link {
color: #fff;
cursor: pointer;
border: 0;
}
}
.homepage {
width: inherit;
height: inherit;
position: absolute;
top: 0;
left: 0;
}
.aboutpage {
@extend .homepage;
}
}
</style>
Explanation of my code
Here is the codeSandbox link, I reproduce the problem in this sandbox, I think this will help people understand my problem better.
https://codesandbox.io/s/vue-page-shift-anime-yp1yf
In this sandbox, I used a vue template. It includes one primary component: App.vue, and 5 other components: Homepage.vue, Aboutpage.vue, Background.vue, Navbar.vue, Navigation.vue. The Background contains navbar and Navigation to form the static elements for the app. Both Homepage and Aboutpage have Background and a unique header. Homepage's header is HOME, Aboutpage's header is ABOUT. Then, I configure the router in the main.js and put router-view tag in the App.vue. I also add the router-link to Navigation.vue. I add a click event (page-shift) to the router-link, this event is emitted to Background.vue. So when user click the link, it will invoke the pageShift method in the Background.vue, cause the animation to happen.
*Main.js
import Vue from "vue";
import App from "./App.vue";
import VueRouter from "vue-router";
import "bootstrap/dist/css/bootstrap.css";
import "animate.css";
import Homepage from "./components/Homepage";
import Aboutpage from "./components/Aboutpage";
Vue.use(VueRouter);
Vue.config.productionTip = false;
const router = new VueRouter({
routes: [
{
path: "*",
component: Homepage
},
{
path: "/about",
component: Aboutpage
}
]
});
new Vue({
render: (h) => h(App),
router
}).$mount("#app");
*App.vue
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "App",
};
</script>
<style>
body {
background: #292929;
}
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</style>
*Background.vue
<template>
<div class="wrap">
<transition
name="page-shift"
enter-active-class="animate__animated animate__slideInLeft animate__faster"
leave-active-class="animate__animated animate__slideOutRight animate__faster"
>
<div v-if="showOverlay" class="overlay"></div>
</transition>
<navigation :menuOpen="menuOpen" @page-shift="pageShift" />
<navbar @open-menu="menuOpen = !menuOpen" />
<div>
<div class="page d-flex justify-content-center align-items-center">
<h1 class="text-light">{{ header }}</h1>
</div>
</div>
</div>
</template>
<script>
import Navbar from "./Navbar";
import Navigation from "./Navigation";
export default {
name: "background",
props: ["header"],
data: function () {
return {
menuOpen: false,
showOverlay: false,
};
},
components: {
Navbar,
Navigation,
},
methods: {
pageShift: function () {
this.showOverlay = !this.showOverlay;
const _this = this;
setTimeout(function () {
_this.menuOpen = !_this.menuOpen;
}, 500);
setTimeout(function () {
_this.showOverlay = !_this.showOverlay;
}, 1000);
},
},
};
</script>
<style lang="scss" scoped>
.wrap {
width: 100%;
height: 100vh;
.overlay {
width: inherit;
height: inherit;
background: #fff;
position: absolute;
z-index: 999;
}
.page {
width: 100%;
height: 100vh;
position: absolute;
top: 0;
left: 0;
}
}
</style>
*Navbar.vue
<template>
<div class="navbar d-flex justify-content-end">
<div
@click="$emit('open-menu')"
class="nav-link font-weight-bold mt-3 btn btn-primary"
>
Menu
</div>
</div>
</template>
<script>
export default {
name: "navbar",
};
</script>
<style lang="scss">
.navbar {
width: 100%;
z-index: 333;
.nav-link {
color: #fff;
cursor: pointer;
border: 0;
}
}
</style>
*Navigation.vue
<template>
<transition
name="slide"
enter-active-class="animate__animated animate__slideInLeft animate__faster"
leave-active-class="animate__animated animate__slideOutLeft animate__faster"
>
<div v-if="menuOpen" class="navigation">
<div
class="container h-100 d-flex justify-content-center align-items-center"
>
<div class="text-center">
<router-link to="/*" class="font-weight-bold text-light"
><h3 @click="$emit('page-shift')">HOME</h3></router-link
>
<router-link to="/about" class="font-weight-bold text-light"
><h3 @click="$emit('page-shift')">ABOUT</h3></router-link
>
</div>
</div>
</div>
</transition>
</template>
<script>
export default {
name: "navigation",
props: ["menuOpen"],
};
</script>
<style lang="scss" scoped>
.navigation {
width: 100%;
height: 100%;
background: #3ecc28;
position: absolute;
z-index: 111;
top: 0;
left: 0;
a:hover {
text-decoration: none;
}
span {
cursor: pointer;
}
}
</style>
*Homepage.vue
<template>
<background :header="'HOME'" />
</template>
<script>
import Background from "./Background";
export default {
name: "homepage",
data: function () {
return {
menuOpen: false,
};
},
components: {
Background,
},
};
</script>
<style lang="scss" scoped>
</style>
Aboutpage.vue
<template>
<background :header="'ABOUT'" />
</template>
<script>
import Background from "./Background";
export default {
name: "aboutpage",
data: function () {
return {
menuOpen: false,
};
},
components: {
Background,
},
};
</script>
<style lang="scss" scoped>
</style>
The issue I have I discovered that if I stay in same router view, the animation will happen. If I go to another view, the page will re-render cause everything reload, so the animation will not happen at all. For example, in the sandbox, if you currently at home, and you click home, the animation will happen; but if you switch from home to about, the page will reload and the animation fails.
What I've tried Well, I am new to vue. I think one solution is to prevent the page render when switch between different views. I do not know how to achieve that. I've tried use a transition tag around the router-view tag in the App.vue, but it doesn't achieve the ideal animation. I've also tried keep-alive, it doesn't work at all. Hope my explanation make sense and I am looking forward to your solutions! Thanks!
