Advertisement

#5 Using the Fade Animation When a Navigation Request is Made

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.

Completing an animation

  • root
    • src
      • components
        • animation
          • types.ts

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,
}
...
(a) Adding a complete entry to our animation stages enum.

Animation is complete

  • root
    • src
      • components
        • animations
          • AnimatableItem.vue

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>
(b) Adding a complete function to our props definition makes it easier to be notified that the animation has completed if that is all you car about otherwise you can still be notified of it along with all of the other stages.

Quick update to the splash screen

  • root
    • src
      • components
        • TheSplashScreen.vue

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>
(c) Updating the template to bind to the complete prop since we only care about when the animation is complete.

TheSplashScreen.vue

<script lang="ts">
...
import {
    AnimatableItem,
    AnimationTypes,
    IAnimateOptions,
} from "@/components/animations";
...
export default class TheSplashScreen extends Vue {
    ...
    private animationComplete() {
        this.isAnimatedOut = true;
    }
    ...
}
</script>
(d) The code for acting on the complete notification is much simpler now.
Advertisement

From where to where

  • root
    • src
      • components
        • routing
          • routing-service.ts

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;
    }
}
(e) Adding the ability to get the current route entry and to find a route entry by a provided route enum value.

Navigation with animation

  • root
    • src
      • components
        • routing
          • TheRouterOutlet.vue

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>
(f) Adding a binding to our template so that we can be notified when an animation has completed.

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>
(g) Playing the fade out animation when a navigation request has been received followed by the actual navigation and then playing the fade in animation.

Testing it out

  • root
    • src
      • views
        • About.vue

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>
(h) Adding a button so that we can perform the navigation on demand instead of on mounted.

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>
(i) Changing the mounted method to the click method.

While we are at it

  • root
    • src
      • views
        • Home.vue

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>
(j) Adding a button to our home view that we can use to navigate to the about view.

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>
(k) Injecting the routing service into our home view allows us to create a click handler that will navigate our app to the about view while performing the animation.
Exciton Interactive LLC
Advertisement