Skip to content

Building a Custom Modal

Ein Modal (bzw. Dialog) ist eine Komponente, die vorerst unsichtbar ist. Sie liegt unter dem Hauptinhalt. Das Modal wird mit einem Klick auf einen Button aktiviert bzw. geöffnet. Die Komponente wird dann über den Hauptinhalt gelegt und der Besucher kann mit dem Modal interagieren.

Custom Modal on Codepen

Das Modal muss daher in einem separaten <div> liegen:

<div class="main"><!-- Main content --></div>
<div class="modal-overlay"><!-- Modal content --></div>

Das Modal hat ein Overlay, das den gesamten Bildschirm bedeckt. Es ist zuerst geschlossen, dafür setzen wir die Opacity auf 0. Wir wollen das geschlossene Overlay außerdem unter den Hauptinhalt schieben. Das machen wir mit folgendem CSS:

.modal-overlay {
/* cover screen */
position: fixed;
inset: 0;
/* make it invisible */
opacity: 0;
/* position it behind main */
z-index: -1;
}

Um das Modal sichtbar zu machen, brauchen wir CSS und JavaScript.

Das Modal-Overlay bekommt eine Opacity von 1 und wird mit z-index nach oben gehievt. Es soll sichtbar sein, wenn <body> die Klasse .modal-is-open erhält:

.modal-is-open .modal-overlay {
/* visible modal */
opacity: 1;
z-index: 1;
}

Wir brauchen nun noch einen Button, den wir anklicken können. JavaScript

  • sucht den Button
  • gibt ihm einen EventListener mit
  • und wenn der EventListener feuert, wird .modal-is-open dem <body> hinzugefügt:
<button class="jsModalButton">Open Modal</button>
const modalButton = document.querySelector('.jsModalButton');
modalButton.addEventListener('click', e => {
document.body.classList.add('modal-is-open');
});

Das Modal muss sich auf mehrere Arten schließen lassen:

  • mit einem Klick auf einen Close-Button im Modal
  • mit einem Klick irgendwo außerhalb des Modals
  • und mit der Escape-Taste (siehe adding keyboard interaction)
const modalCloseButton = document.querySelector('.jsModalCloseButton');
modalCloseButton.addEventListener('click', e => {
document.body.classList.remove('modal-is-open');
});

Wenn wir außerhalb des Modals klicken, klicken wir eigentlich auf das Modal Overlay.

Natürlich können wir jetzt einfach sagen, wir legen einen EventListener auf das Modal Overlay und wenn jemand draufklickt, entfernen wir die Klasse .modal-is-open vom body.

Allerdings wird das Modal auch geschlossen, wenn wir irgendwohin innerhalb des Modals klicken – weil der click Event vom Modal nach oben bubbelt und das Modal Overlay erreicht und dort den EventListener triggert. Zack, Modal closed. Wir schauen uns das in der Konsole an:

modalOverlay.addEventListener('click', e => {
document.body.classList.remove('modal-is-open');
console.log(e.target);
});
// Konsole bei Klick auf die Überschrift im Modal:
<h2>I am a Modal!</h2>

Das darf nicht passieren. Eine Möglichkeit wäre, die Methode stopPropagation() auf das Modal anzuwenden:

modal.addEventListener('click', e => {
e.stopPropagation();
});

Der Nachteil ist, dass nun vielleicht auch Events gestoppt werden, die nicht gestoppt werden sollen. Es könnte ein EventListener auf dem <body> liegen, der bei einem Klick auf das Modal am Bubbeln gehindert wird. Er erreicht also niemals sein Ziel.

Schauen wir uns noch einmal an, was bei einem Klick genau passiert. Der User kann auf drei verschiedene Dinge klicken:

  • auf ein Element innerhalb des Modals
  • auf das Modal selbst
  • auf das Modal Overlay

Jedes dieser drei Events wird nach oben bubbeln und das Modal Overlay erreichen. Aber Events innerhalb des Modals und im Modal selbst erreichen zuerst das Modal, dann das Overlay:

  • Element > Modal > Modal Overlay
  • Modal -> Modal Overlay
  • Overlay

Wir können prüfen, ob das Event durch das Modal bubbelt. Wenn es durch das Modal bubbelt, dürfen wir das Modal nicht schließen. Genauer gesagt können wir nicht direkt prüfen, ob es bubbelt, aber wir können prüfen, ob das Modal ein Vorfahr des event.target ist. Wenn das Modal ein Vorfahr ist, wissen wir, dass das Event durch das Modal durchbubbeln wird.

Dazu benutzen wir closest. closest prüft auch, ob das event.target mit dem gegebenen Selektor übereinstimmt (weil closest immer beim aktuell geklickten Element startet).

modalOverlay.addEventListener('click', e => {
if (e.target.closest('.modal')) {
// do nothing
} else {
// close modal
document.body.classList.remove('modal-is-open');
}
});

Das geht noch ein bisschen kürzer mit dem NOT Operator:
Wenn der Vorfahr des geklickten Elements nicht das Modal ist, oder das Modal selbst angeklickt wurde, entferne .modal-is-open aus dem <body>:

modalOverlay.addEventListener('click', e => {
if (!e.target.closest('.modal')) {
document.body.classList.remove('modal-is-open');
}
});