#5 Using the Fade Animation When a Navigation Request is Made
Saturday, July 6, 2019
In this article we will start by making a change to the animatable item that we created previously so that it is easier to be notified when an animation has completed. Once we have finished that we will add the ability for our navigation service to tell us what route we are currently on and to get the route for our destination. We will finish up by adding the ability to perform the fade out and fade in animations in coordination with a route change.
Parts
- Part 45: Adjusting Shares
- Part 44: Plan Percentages
- Part 43: Home Securities
- Part 42: Updating Plans
- Part 41: Plan Details View
- Part 40: Portfolio Getters
- Part 39: Portfolio Plan
- Part 38: Portfolio Category
- Part 37: Account Securities
- Part 36: Account Transfer
- Part 35: View Account Security
- Part 34: Updating Deposit
- Part 33: View Account Deposit
- Part 32: Display Account Details
- Part 31: Account Getters
- Part 30: Deposits And Securities
- Part 29: Add Accounts Details
- Part 28: Refactoring Accounts
- Part 27: Add Security Models
- Part 26: Edit Security Details
- Part 25: View Security Details
- Part 24: Navigating To Details
- Part 23: Getters Validation
- Part 22: Query Parameters
- Part 21: Tab Entries
- Part 20: Tab Header
- Part 19: List View
- Part 18: Vuex Getters
- Part 17: End Domain Model
- Part 16: Start Domain Model
- Part 15: Pop Routes
- Part 14: Push Routes
- Part 13: Removing Accounts
- Part 12: Vuex (Decorators)
- Part 11: Vuex (Accounts)
- Part 10: The App Bar (Settings)
- Part 9: Remove Consumer Rxjs
- Part 8: The App Bar (Back)
- Part 7: Structuring Our App
- Part 6: Animation Between Views
- Part 5: Navigation Fade
- Part 4: Navigation Requests
- Part 3: Fade Animations (cont.)
- Part 2: Fade Animations
- Part 1: Splash Screen
Completing an animation
- root
- src
- components
- animation
- types.ts
- animation
- components
- src
In a previous article we created the ability to be notified of the current stage of an animation. While the system works we can make it a little more user friendly if we had a simple way of saying that the animation had completed. To do this we will first add a complete entry to our animation stages enum (a).
types.ts
export enum AnimationStages {
...,
Complete,
}
...
Animation is complete
- root
- src
- components
- animations
- AnimatableItem.vue
- animations
- components
- src
With the addition to our stages enum we also need to add another function to the props definition of our animatable object and call it when the animation has completed (b). By doing this we can be notified that the animation has completed no matter which method we choose to use.
AnimatableItem.vue
<script lang="ts">
...
@Component({
props: {
complete: {
type: Function,
default: () => { return; },
},
...,
},
})
export default class AnimatableItem extends Vue {
public $props!: Props<{
complete: () => void;
update: (x: AnimationStages) => void;
}>;
...
private animationEnd() {
...
requestAnimationFrame(() => {
this.updateWithPreAndPost(
AnimationStages.ActiveRemovePre,
AnimationStages.ActiveRemovePost,
() => this.activeCssClass = "");
this.$props.complete();
this.$props.update(AnimationStages.Complete);
});
...
}
...
}
</script>
Quick update to the splash screen
- root
- src
- components
- TheSplashScreen.vue
- components
- src
In the template of our splash screen we can just bind to the complete prop (c) and of course add the method to our script (d).
TheSplashScreen.vue
<template lang="pug">
AnimatableItem.splash-screen(
v-bind:complete="animationComplete"
v-bind:duration="'500ms'"
v-bind:subject="animationSubject"
v-if="!isAnimatedOut")
img(alt="Vue logo" src="../assets/logo.png")
p Welcome to Your Vue.js + TypeScript App
</template>
TheSplashScreen.vue
<script lang="ts">
...
import {
AnimatableItem,
AnimationTypes,
IAnimateOptions,
} from "@/components/animations";
...
export default class TheSplashScreen extends Vue {
...
private animationComplete() {
this.isAnimatedOut = true;
}
...
}
</script>
From where to where
- root
- src
- components
- routing
- routing-service.ts
- routing
- components
- src
We will eventually want the ability to determine what animation is played depending on where we are and where we are heading to. To facilitate this we just need to add a little bit of code to our routing service (e).
routing-service.ts
...
export class RoutingService {
...
public get current() {
return this.entryByName(this.router.currentRoute.name as string);
}
...
public find = (route: Routes) => {
const entry = this._routes.get(route);
if (typeof(entry) === "undefined") {
throw new Error(`A route for (${route}) ${Routes[route]} has not been defined.`)
}
return entry;
}
...
private entryByName = (name: string) => {
const route = this._values.find((x) => x.name === name);
if (typeof(route) === "undefined") {
throw new Error(`A route with the name: ${name} has not been defined.`);
}
return route;
}
}
Navigation with animation
- root
- src
- components
- routing
- TheRouterOutlet.vue
- routing
- components
- src
Next we return to our router outlet and first update our template (f) so that we can be notified when an animation has completed.
TheRouterOutlet.vue
<template lang="pug">
AnimatableItem.router-view(
v-bind:complete="animationComplete"
v-bind:subject="animationSubject")
router-view/
</template>
And then update our script it so that we receive a navigation request we play the fade out animation and once that is completed we perform a route navigation and then play the fade in animation (g).
TheRouterOutlet.vue
<script lang="ts">
...
import { Subject } from "rxjs";
import {
AnimatableItem,
AnimationTypes,
IAnimateOptions,
} from "@/components/animations";
import { RouteEntry, ...} from "@/components/routing";
@Component({
components: {
AnimatableItem,
},
})
export default class TheRouterOutlet extends Vue {
...
private readonly animationSubject = new Subject<IAnimateOptions>();
private isAnimatingOut = false;
private toEntry!: RouteEntry;
private animate(route: Routes) {
this.isAnimatingOut = true;
this.toEntry = this.routingService.find(route);
this.animationSubject.next({ type: AnimationTypes.FadeOut });
}
private animationComplete() {
if (this.isAnimatingOut === false) {
return;
}
this.isAnimatingOut = false;
this.$router.push(this.toEntry.path);
this.animationSubject.next({ type: AnimationTypes.FadeIn });
}
private beforeDestroy() {
this.animationSubject.complete();
this.routingService.complete();
}
private created() {
this.routingService
.navigate$
.subscribe(this.animate);
}
}
</script>
Testing it out
- root
- src
- views
- About.vue
- views
- src
To test things out we will modify the template of our about view so that it includes a button that we can press to that we can perform the navigation on demand instead of when the view is mounted (h).
About.vue
<template lang="pug">
div.about
h1 This is an about page
button(v-on:click.prevent="click") Test
</template>
With the template changed we just need to change the mounted
method to a click
method (i)
and press the button.
About.vue
<script lang="ts">
...
export default class About extends Vue {
...
private click() {
this.routingService.navigateTo(Routes.Home);
}
}
</script>
While we are at it
- root
- src
- views
- Home.vue
- views
- src
In order to be able to test out animations to and from the left and right sides of our app we are going to modify our home view in much the same way that we did the about view. We will start by once again adding a button to the template (j). We are also going to remove the logo as well but that is just because its distracting when it loads in.
Home.vue
<template lang="pug">
div.home
button(v-on:click.prevent="click") Test
HelloWorld(msg="Welcome to Your Vue.js + TypeScript App")/
</template>
Lastly we make the same changes to the script that we did to the script in the about view except that we indicate that we want to navigate to the about view when the button is clicked instead of the home view (k).
Home.vue
<script lang="ts">
import { ..., Inject,.. } from "vue-property-decorator";
...
import { Routes, RoutingService } from "@/components/routing";
...
export default class Home extends Vue {
@Inject() private readonly routingService!: RoutingService;
private click() {
this.routingService.navigateTo(Routes.About);
}
}
</script>