Advertisement

#10 Adding An Animated Settings Menu

In this article we focus on adding an animated settings menu to our app bar. Initially this settings menu will contain our anchor tag for accessing the about page. We create it such that the menu can be toggled visible and hidden using the cog button on the right hand side of the app bar. We will also add the ability for the menu to be closed if and or when the user navigates to another page while the menu is open. We will then finish up by modifying our navigation service so that if the user tries to navigate to the current page we will just skip the navigation.

Adding A Settings Menu (Template)

  • root
    • src
      • components
        • app-bar
          • TheAppBarSettings.vue

We will start by adding a little bit of markup to the template for our app bar settings (a). As a note we can see that we have moved the button with a cog icon into this template from the app bar itself.

TheAppBarSettings.vue

<template lang="pug">
div.settings-container
    button.cog
        FontAwesomeIcon(icon="cog")/
    AnimatableItem.settings(
        v-bind:subject="subject")
        a(v-on:click.prevent="navigateToAbout") About
</template>
(a) The initial template for our settings button and its associated menu.

Adding A Settings Menu (Script)

  • root
    • src
      • components
        • app-bar
          • TheAppBarSettings.vue

For the script we will start by just making sure that we can navigate to the about page when the anchor tag is clicked (b). For the moment we are not going to do any animation but we will take care of that shortly.

TheAppBarSettings.vue

<script lang="ts">
import { Component, Inject, Vue } from "vue-property-decorator";

import {
    AnimatableItem,
    AnimationSubject,
} from "@/components/animations";

import { 
    Routes,
    RoutingService,
} from "@/components/routing";

@Component({
    components: {
        AnimatableItem,
    },
})
export default class TheAppBarSettings extends Vue {
    @Inject() private readonly routingService!: RoutingService;

    private readonly subject = new AnimationSubject();

    private navigateToAbout() {
        this.routingService.navigateTo(Routes.About);
    }
}
</script>
(b) The initial script for our settings button and its associated menu.

Adding A Settings Menu (Style)

  • root
    • src
      • components
        • app-bar
          • TheAppBarSettings.vue

Last but not least for our initial setup we will do just a bit of styling (c). As I mentioned when we defined the template we have moved the cog button into this component so we need to include the styling that we were applying in the app bar component.

TheAppBarSettings.vue

<style lang="sass" scoped>
@import "../../bourbon/bourbon"
@import "../../bitters/functions"
@import "../../bitters/variables"

.settings-container
    position: relative

.cog
    flex: 0
    @include padding(0.75rem 1rem)
    min-height: rem(45.33px)

.settings
    @include position(absolute, 100% 0 null null)
    background-color: $viewport-background-color

    & > a
        display: block
        @include padding(1rem)
</style>
(c) The initial style for our settings button and its associated menu.

Adding the settings component to our app bar (Template)

  • root
    • src
      • components
        • app-bar
          • TheAppBar.vue

Now that we have defined the component we can add it to the template of our app bar (d)

TheAppBar.vue

<template lang="pug">
div.app-bar
    ...
    Settings/
</template>
(d) Adding the app bar settings component to the template of our app bar.

Adding the settings component to our app bar (Script)

  • root
    • src
      • components
        • app-bar
          • TheAppBar.vue

Of course for everything to work we need to import and add the app bar settings component to the component definition for our app bar (e).

TheAppBar.vue

<script lang="ts">
...
import Settings from "@/components/app-bar/TheAppBarSettings.vue";
...
@Component({
    components: {
        Settings,
    },
})
export default class TheAppBar extends Vue {
    ...
}
</script>
(e) Importing the app bar settings component and adding it to the component definition of our app bar.

Adding the settings component to our app bar (Style)

  • root
    • src
      • components
        • app-bar
          • TheAppBar.vue

Since we will have our settings menu animating in and out we might as well adjust the z index of our app bar itself (f).

TheAppBar.vue

<style lang="sass" scoped>
...
.app-bar
    ...
    z-index: 9000

.angle-left, .cog // <-- move the cog styling to the app bar settings
    ...
...
</style>
(f) Adjusting the z index of our app bar so that we can make sure that our settings menu is visible when it is open.

Toggling the settings menu (Template)

  • root
    • src
      • components
        • app-bar
          • TheAppBarSettings.vue

Next up is actually being able to toggle the menu in and out by pressing the cog button. Starting with adding the click handler to the button template (g).

TheAppBarSettings.vue

<template lang="pug">
div.settings-container
    button.cog(v-on:click.prevent="toggle")
        ...
    ...
</template>
(g) Adding a click handler to our cog button.

Toggling the settings menu (Script)

  • root
    • src
      • components
        • app-bar
          • TheAppBarSettings.vue

Adding the ability to animate our settings menu in and out is a simple matter of using our animation subject along with a boolean is visible flag to decided which animation to run (h). In this case we are going to be animating the menu in from the right and out to the right.

TheAppBarSettings.vue

<script lang="ts">
...
import {
    ...
    AnimationTypes,
} from "@/components/animations";
...
export default class TheAppBarSettings extends Vue {
    ...
    private isVisible = false;

    private toggle() {
        switch (this.isVisible) {
            case true:
                this.subject.next(AnimationTypes.TranslateOutToRight);
                this.isVisible = false;
                break;
            case false:
                this.isVisible = true;
                this.subject.next(AnimationTypes.TranslateInFromRight);
                break;
        }
    }
}
</script>
(h) Adding animations to our settings menu based on whether we are toggling it in or out.
Advertisement

Toggling the settings menu (Style)

  • root
    • src
      • components
        • app-bar
          • TheAppBarSettings.vue

The only thing we need to do to the styling, at the moment, is to start our menu off screen out to the right (i).

TheAppBarSettings.vue

<style lang="sass" scoped>
...
.settings
    ...
    transform: translateX(100%)
</style>
(i) Adding translation to our styling to start our menu out to the right.

Making tab stops work correctly

  • root
    • src
      • components
        • app-bar
          • TheAppBarSettings.vue

As it stands right now even if the menu is offscreen you can still get to it by using the tab key. In addition to this since our anchor tag does not possess an href attribute you can not tab to it. To fix both of these problems we will use a vue if directive to only add the menu to the DOM if it is visible and add a place holder to the href for the anchor (j).

TheAppBarSettings.vue

<template lang="pug">
div.settings-container
    ...
    AnimatableItem.settings(
        ...
        v-show="isVisible")
        a(
            href="#void"
            ...) About
</template>
(j) Using a vue if directive to add the menu only when it is visible and a place holder href attribute to our anchor so that you can tab to it.

Closing the menu when the user clicks the link

  • root
    • src
      • components
        • app-bar
          • TheAppBarSettings.vue

Next up I would like for the menu to close if/when the user clicks on a navigation link within the menu. To do this we will simple add an open and a close helper methods and subscribe to the navigate stream of the routing service (k). Also now that we are using the vue if directive we need to pass in an options object to our out animation so that the menu is only removed once the animation is complete. Lastly just to err on the side of caution we are setting the is visible flag to true in the open method and waiting for the next animation frame before starting the animation.

TheAppBarSettings.vue

<script lang="ts">
...
import {
    ...,
    IAnimationSubjectOptions,
} from "@/components/animations";
...
export default class TheAppBarSettings extends Vue {
    ...
    private readonly animationOptionsClose: IAnimationSubjectOptions = {
        complete: this.closed,
    };
    ...
    private close() {
        if (this.isVisible === false) {
            return;
        }
        this.subject.next(AnimationTypes.TranslateOutToRight, this.animationOptionsClose);
    }

    private closed() {
        this.isVisible = false;
    }

    private created() {
        this.routingService
            .navigate$
            .subscribe(this.close);
    }
    ...
    private open() {
        if (this.isVisible) {
            return;
        }
        this.isVisible = true;
        requestAnimationFrame(() => {
            this.subject.next(AnimationTypes.TranslateInFromRight);
        });
    }

    private toggle() {
        switch (this.isVisible) {
            case true:
                this.close();
                break;
            case false:
                this.open();
                break;
        }
    }
}
</script>
(k) Updating our script so that the menu will close if/when the user clicks on a link.

Hiding the overflow

  • root
    • src
      • App.vue

I thought we had already taken care of the overflow on the app div previously but looks like I forgot to do it. Either way to make sure that we do not see the horizontal scrollbar when we animating things in and out we just need to hide the overflow (l).

App.vue

<style lang="sass">
...
#app
    ...
    overflow: hidden
...
</style>
(l) Hiding the overflow so that we do not see the horizontal scrollbar when animating things.

If we are already there no need to navigate

  • root
    • src
      • components
        • routing
          • route-entry.ts

Right now as it stands even if we are already on a page we can still issue a request to navigate to it. To prevent this from happening we just need to modify our route class to add a method that will tell us given an entry from the routes enum whether or not it is associated with a particular route entry (m).

route-entry.ts

...
export class RouteEntry {
    ...
    public isSameRoute = (route: Routes) => {
        return this._route === route;
    }
}
(m) Adding a method to our route entry that will tell us if the provide routes enum entry points to it.

Skip the navigation

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

With the change to our route entry we can update our routing service to check whether or not the requested navigation would point to the page we are already on. If it does we can just skip the navigation (n).

routing-service.ts

...
export class RoutingService {
    ...
    public navigateTo = (to: Routes) => {
        if (this.current.isSameRoute(to)) {
            return;
        }
        this._navigate.next(to);
    }
    ...
}
(n) Skipping the navigation if it would result in us navigating to the current page.
Exciton Interactive LLC
Advertisement