Blog / Afficher un modal avec VueJS et son composant Teleport
4 min - 02 Oct 2023
TL;DR: Comment implémenter rapidement un modal avec VueJS en utilisant son composant intégré Teleport.
Vous trouverez le code source via ce Github Repository. Découvrez en plus sur Capsules ou X.
Utiliser un modal peut sembler évident sur un site web, sa mise en place peut parfois s'avérer complexe. Pour simplifier cette tâche, le framework Vue a mis en place son composant intégré <Teleport>
. Celui-ci nous permet de "téléporter" une partie du modèle d'un composant dans un nœud du DOM existant en dehors de la hiérarchie du DOM de ce composant.
Déterminons maintenant l'emplacement de ce nœud externe en attribuant l'id capsules
à Welcome.vue
.
/resources/js/pages/Welcome.vue
<script setup>
import logotype from '/public/assets/capsules-logotype-red-blue-home.svg';
</script>
<template>
<div id="capsules" class="w-full min-h-screen flex flex-col font-sans text-primary-black">
<div class="grow mx-8 lg:mx-auto max-w-screen-lg overflow-auto flex flex-col items-center justify-center text-center">
<img class="w-24 h-24" v-bind:src="logotype">
<h1 class="mt-4 text-6xl font-bold select-none header-mode" v-text="'Capsules Codes'" />
</div>
</div>
</template>
Ce composant Vue est en réalité une page InertiaJS appelée lors de l’appel à la route principale indiquée dans le fichier web.php
routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
Route::get( '/', fn() => Inertia::render( 'Welcome' ) );
Avec l'id capsules
maintenant attribué, le composant Modal.vue
va pouvoir utiliser le composant intégré <Teleport>
sur cet élément.
/resources/js/components/Modal.vue
<script setup>
import { ref, onMounted } from 'vue';
const props = defineProps( { open : { type : Boolean, default : false } } );
const emits = defineEmits( [ 'toggle' ] );
const ready = ref( false );
function toggle()
{
emits( 'toggle' );
}
onMounted( () => ready.value = true );
</script>
<template>
<Teleport to="#capsules" v-if="ready">
<Transition enter-active-class="duration-500 ease-in-out" enter-from-class="opacity-0" enter-to-class="opacity-100" leave-active-class="duration-500 ease-in" leave-from-class="opacity-100" leave-to-class="opacity-0">
<div v-if="props.open" class="fixed w-full h-full flex items-center justify-center backdrop-blur-[1px] bg-primary-white bg-opacity-50" v-on:click="toggle()">
<div class="relative m-16 p-2 rounded-xl flex flex-wrap items-center justify-center text-xs bg-white whitespace-pre shadow-2xl shadow-black/10" v-on:click.stop>
<slot />
</div>
</div>
</Transition>
</Teleport>
</template>
La variable ready
permet de charger le composant afin qu'il puisse déclencher sa Transition
de la manière la plus fluide possible.
L'événement v-on:click.stop
, quant à lui, empêche toute propagation éventuelle du clic à d'autres éléments que le modal lui-même.
Le composant intégré <Transition>
peut être utilisé pour appliquer des animations d'entrée et de sortie sur des éléments ou des composants qui lui sont transmis via son slot par défaut. Dans ce cas, il s'agit d'une transition en douceur de l'opacité.
La configuration du modal étant achevée, il est désormais temps de créer un composant qui permettra d'afficher ce modal. Dans ce cas-ci : un bouton.
resources/js/components/Button.vue
<script setup>
import { ref, watch } from 'vue';
import Modal from '/resources/js/components/Modal.vue';
import logotype from '/public/assets/capsules-logotype-red-blue-home.svg';
const button = ref();
const isModalOpen = ref( false );
watch( () => isModalOpen.value, () => isModalOpen.value ? window.addEventListener( 'click', clickOutside ) : window.removeEventListener( 'click', clickOutside ) );
function clickOutside( event )
{
if( event.target === button.value || !event.composedPath().includes( button.value ) ) isModalOpen.value = false;
}
</script>
<template>
<div ref="button" class="m-4">
<button class="px-4 py-2 text-sm rounded-md border border-primary-black hover:border-primary-red hover:text-primary-red transition-all" v-on:click="isModalOpen = true" v-bind:class="{ 'opacity-25' : isModalOpen }" v-bind:disabled="isModalOpen" v-text="'Open Modal'" />
<Modal v-bind:open="isModalOpen" v-on:toggle="isModalOpen = false">
<div class="p-8 flex flex-row space-x-4 rounded-lg">
<img class="w-12 h-12 select-none" v-bind:src="logotype">
<div class="font-mono flex items-center">
<h2 class="text-lg align-middle" v-text="'A wild MODAL appeared!'"/>
</div>
</div>
</Modal>
</div>
</template>
Une variable button
est créée et associée à la div parente du Modal
, ce qui permet de surveiller tout clic effectué en dehors de ce composant. Un watcher
est utilisé pour observer la variable isModalOpen
afin de déterminer si la fonction clickOutside
doit être déclenchée lors d'un clic sur l'écran. Si le Modal
est ouvert, la fonction est activée, sinon, elle ne l'est pas.
Le composant Button.vue
peut être ajouté à la page Welcome.vue
resources/js/pages/Welcome.vue
<script setup>
import logotype from '/public/assets/capsules-logotype-red-blue-home.svg';
import Button from '~/components/Button.vue';
</script>
<template>
<div id="capsules" class="w-full min-h-screen flex flex-col font-sans text-primary-black">
<div class="grow mx-8 lg:mx-auto max-w-screen-lg overflow-auto flex flex-col items-center justify-center text-center">
<img class="w-24 h-24" v-bind:src="logotype">
<h1 class="mt-4 text-6xl font-bold select-none header-mode" v-text="'Capsules Codes'" />
<Button class="pt-8" />
</div>
</div>
</template>
Le modal s’affiche lors du clic sur le bouton. Il est maintenant possible de personnaliser le Modal
à sa guise : sa position, ses dimensions, ses actions. Le composant de Reaction implémenté sur le Blog Capsules Codes est aussi le fruit de cette méthode.
Ravi d’avoir pu aider.