Dynamischer OffCanvas Inhalt mit portal-vue

      0

PortalVue Logo

Auf der Arbeit wurde ich mit dem Problem konfrontiert, dynamische Inhalte in einem OffCanvas darzustellen. Beispielsweise ein Inhaltsverzeichnis oder die mobile Hauptnavigation. Insgesamt gab es über fünf verschiedene Fälle, die abgedeckt werden sollten. Normalerweise würde man vermutlich eine theme Property an die entsprechende Komponente übergeben. Damit wären die Inhalte aber nicht mehr im Kontext ihrer Komponenten und die OffCanvas Komponente würde immer größer werden. Eine Alternative bietet portal-vue.

Mit portal-vue kann man Elemente an beliebiger Stelle im DOM rendern. Als Beispiel nutzen wir die beiden oben genannten Fälle: Inhaltsverzeichnis und Hauptnavigation. Das Ziel ist es die Inhalte dieser beiden Komponenten an einer zentralen Stelle in der App.vue auszuspielen.

Disclaimer: Bei den Beispielen in diesem Beitrag handelt es sich um eine stark vereinfachte Version des Problems. Außerdem gehe ich davon aus, dass du entweder schon ein Vue Projekt aufgesetzt hast oder weißt wie das geht.

portal-vue installieren

Installier portal-vue mit einem der folgenden Befehle.

yarn add portal-vue
npm install --save portal-vue

Anschließend fügst du die folgenden Zeilen in deine main.js ein.

import PortalVue from 'portal-vue'

Vue.use(PortalVue)

Konfiguration

Anschließend erstellen wir insgesamt drei Komponenten über das Terminal:

touch src/components/OffCanvas.vue
touch src/components/MainNavigation.vue
touch src/components/TableOfContents.vue

Nun haben wir alles, was wir brauchen und können uns um den Code kümmern. Als erstes konfigurieren wir den zentralen Ort, wo die Inhalte – also der OffCanvas – angezeigt werden sollen. Dazu binden wir unsere neuen Komponenten ein.

// App.vue
<template>
    <div id="app">
        <portal-target name="offCanvas" />
        <main-navigation />
        <table-of-contents />

        <div id="content">
            <h2 id="headline1">Headline 1</h2>
            <p>Lorem ipsum dolor sit amet.</p>
            <h2 id="headline2">Headline 2</h2>
            <p>Lorem ipsum dolor sit amet.</p>
            <h2 id="headline3">Headline 3</h2>
            <p>Lorem ipsum dolor sit amet.</p>
        </div>
    </div>
</template>

<script>
import MainNavigation from '@/components/MainNavigation'
import TableOfContents from '@/components/TableOfContents'

export default {
    components: { MainNavigation, TableOfContents }
}
</script>

Dem Portal haben wir den Namen „offCanvas“ genannt. Diesen müssen wir uns für unser Inhaltsverzeichnis und unsere Hauptnavigation merken. Aber kümmern wir uns erst einmal um den OffCanvas.

// OffCanvas.vue

<template>
    <div>
        <div v-if="show" class="backdrop" @click="close" />
        <div
            :style="show ? 'visibility: visible;' : 'visibility: hidden;'"
            :class="`offcanvas${show ? ' show' : ''}`"
        >
            <div class="offcanvas__header">
                <h2>{{ headline }}</h2>
                <button title="Close" @click="close">X</button>
            </div>

            <div class="offcanvas__body">
                <slot />
            </div>
        </div>
    </div>
</template>

<script>
export default {
    name: 'OffCanvas',

    props: {
        headline: { type: String, required: true },
    },

    computed: {
        show () {
            return this.$store.getters.showOffCanvas
        },
    },

    methods: {
        close () {
            this.$store.commit('toggleOffCanvas', false)
            this.$emit('close')
        },
    },
}
</script>

<style lang="scss" scoped>
    .backdrop {
        position: fixed;
        top: 0;
        left: 0;
        height: 100vh;
        width: 100vw;
        background-color: rgba(0, 0, 0, 0.8);
        z-index: 100;
    }

    .offcanvas {
        position: fixed;
        top: 0;
        left: 0;
        height: 100vh;
        width: 20%;
        background-color: #fff;
        z-index: 101;
        border-right: 1px solid rgba(0, 0, 0, 0.2);
        transition: transform 0.3s ease-in-out;
        transform: translateX(-100%);

        &.show {
            transform: none;
        }

        &__header {
            display: flex;
            justify-content: space-between;
        }
    }
</style>

Die OffCanvas Komponente bekommt eine headline mit. Ob der OffCanvas angezeigt wird oder nicht, wird über den Vuex Store entschieden, aber dazu später mehr. Zunächst binden wir diesen Code in unsere Hauptnavigation und unser Inhaltsverzeichnis ein.

OffCanvas einbinden

// MainNavigation.vue

<template>
    <div>
        <button title="open menu" @click="openMenu">Open menu</button>

        <portal v-if="usePortal" to="offCanvas">
            <off-canvas headline="Main navigation" @close="usePortal = false">
                MAIN NAVIGATION CONTENT
            </off-canvas>
        </portal>
    </div>
</template>

<script>
import OffCanvas from '@/components/OffCanvas'


export default {
    name: 'MainNavigation',

    components: { OffCanvas },

    data: () => ({ usePortal: false }),

    methods: {
        openMenu () {
            this.usePortal = true
            this.$store.commit('toggleOffCanvas', true)
        },
    },
}
</script>
// TableOfContents.vue

<template>
    <div>
        <button @click="openOffCanvas">Table of contents</button>

        <portal v-if="usePortal" to="offCanvas">
            <off-canvas headline="Table Of Contents" @close="usePortal = false">
                <ul @click="$store.commit('toggleOffCanvas', false)">
                    <li><a href="#headline1">Headline 1</a></li>
                    <li><a href="#headline2">Headline 2</a></li>
                    <li><a href="#headline3">Headline 3</a></li>
                </ul>
            </off-canvas>
        </portal>
    </div>
</template>

<script>
import OffCanvas from '@/components/OffCanvas'


export default {
    name: 'TableOfContents',

    components: { OffCanvas },

    data: () => ({ usePortal: true }),

    methods: {
        openOffCanvas () {
            this.usePortal = true
            this.$store.commit('toggleOffCanvas', true)
        },
    },
}
</script>

Zu guter Letzt zeige ich euch noch die Vuex Store Konfiguration.

// src/store/index.js

export const state = () => ({
    showOffCanvas: false,
})

export const mutations = {
    toggleOffCanvas (state, show) {
        state.showOffCanvas = show
    },
}

export const getters = {
    showOffCanvas: state => state.showOffCanvas,
}

Fertig! Beim Klick auf einen der beiden Buttons öffnet sich der OffCanvas inklusive Backdrop und dem dazugehörigen Inhalt.

Hat euch der Beitrag geholfen? Schreibt es gerne in die Kommentare. :)

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert