Opening the modal
Wenn der User auf einen Button klickt, müssen wir wissen, welches Modal geöffnet werden soll. Wir können den Button und das zugehörige Modal an Modal übergeben.
Modal({ modal: document.querySelector('#user-triggered-modal'); button: document.querySelector('#user-triggered-modal-button');});Diese Eigenschaften können wir über ein Settings-Objekt akzeptieren:
export default function Modal(settings) { const { button, modal } = settings,}Writing openModal
Section titled “Writing openModal”Um das Modal zu öffnen, müssen wir is-open zum Overlay hinzufügen. Das Overlay finden wir über das modal-Element (das Overlay ist das Elternelement des Modals).
export default function Modal(settings) { const { button, modal } = settings; const overlay = modal.parentElement; console.log(overlay);}Nun müssen wir eine Funktion openModal innerhalb von modal.js erstellen. Dazu gibt es mehrere Möglichkeiten.
- Erstellung außerhalb von
Modal - Erstellung innerhalb on
Modal - als Methode erstellen
Jeder Ansatz hat seine Vor- und Nachteile.
Wenn die Funktion außerhalb von Modal erstellt wird, sieht der Code ungefähr so aus:
export default function Modal(settings) { // ...}
function openModal() { // ...}Der Nachteil von diesem Ansatz ist es, dass man den lexical Scope nicht nutzen kann. Wir müssen Variablen von Modal nach openModal übergeben:
export default function Modal(settings) { // ... openModal(overlay);}
function openModal(overlay) { overlay.classList.add('is-open');}Wenn wir openModal innerhalb von Modal deklarieren, müssen wir keine Variablen übergeben, sondern können den lexical Scope nutzen. Allerdings ist dieses Verschachteln von Funktionen etwas unordentlich:
export default function Modal(settings) { // ...
function openModal() { overlay.classList.add('is-open'); }
// Opening the modal openModal();}Der Code wird deutlich einfacher zu lesen, wenn alle Funktionen innerhalb eines Objekts platziert sind, also openModal als Methode erstellt wird. Der Name der Komponente (modal) wird als Name für das Objekt verwendet, da das einfacher zu verstehen ist:
export default function Modal(settings) { // ...
const modal = { openModal() { overlay.classList.add('is-open'); }, };
// Opening the modal modal.openModal();}Wenn wir das so machen, müssen wir die Funktion nicht mehr openModal nennen, weil es offensichtlich ist, dass wir das Modal öffnen, wenn wir modal.open schreiben. Wir können daher openModal zu open umbenennen:
export default function Modal(settings) { // ...
const modal = { open() { overlay.classList.add('is-open'); }, };
// Opening the modal modal.open();}Damit diese Lösung funktioniert, müssen wir die Variable modal, die über die Settings übergeben wird, umbenennen. Wir können dazu Element als Suffix ergänzen, somit wissen wir, dass wir es mit einem Element zu tun haben.
export default function Modal(settings) { const modalElement = settings.modal; // ...
const modal = { // ... }}Um diesen Prozess einfacher zu machen, können wir den User bitten, modalElement statt modal zu übergeben:
Modal({ modalElement: document.querySelector('#user-triggered-modal'), buttonElement: document.querySelector('#user-triggered-modal-button'),});export default function Modal(settings) { const { modalElement, buttonElement } settings; // ...
const modal = { // ... }}Außerdem sollten wir overlay zu overlayElement umbenennen, um bei der Namenskonvention zu bleiben:
export default function Modal(settings) { const { modalElement, buttonElement } = settings; const overlayElement = modalElement.parentElement;
const modal = { // ... }}Opening the Modal
Section titled “Opening the Modal”Wenn der User einen Button anklickt, müssen wir das Modal öffnen. Das heißt, wir brauchen einen EventListener. Um es einfach zu halten, können wir diesen innerhalb von Modal platzieren.
export default function Modal(settings) { // declare variables // declare methods
buttonElement.addEventListener('click', e => { modal.open(); });}Um sicherzugehen, dass open aufgerufen wird, prüfen wir mit einem console.log Statement:
export default function Modal(settings) { // declare variables
const modal = { open() { console.log('Opening modal'); }, };
// add eventListeners buttonElement.addEventListener('click', e => { modal.open(); });}Beim Öffnen des Modals müssen 6 Dinge passieren:
- eine Klasse zum öffnen hinzufügen
- die Hand muss winken
- das erste Element fokussieren
- den Fokus einfangen
aria-expandedmuss auftruegesetzt werden- Screenreader mit
aria-hiddenvon anderen Elementen aussperren
All diese Schritte sehen wir im Code, den wir vorher geschrieben haben:
// Previous codefunction openModal() { document.body.classList.add('modal-is-open');
wave();
const input = modal.querySelector('input'); input.focus();
document.addEventListener('keydown', trapFocus); modalButton.setAttribute('aria-expanded', 'true'); main.setAttribute('aria-hidden', 'true');}Adding the opening class
Section titled “Adding the opening class”In der vorhergehenden Lektion haben wir dieses HTML verwendet:
<div class="modal-overlay"> <div class="modal">...</div></div>
<div class="modal-overlay"> <div class="modal">...</div></div>Das bedeutet, dass wir is-open zum Overlay hinzufügen müssen, um das Overlay zu öffnen:
export default function Modal(settings) { // Declare variables
const modal = { open() { overlay.Element.classList.add('is-open'); }, };
// Add event listeners}Waving the hand
Section titled “Waving the hand”Wir haben eine Funktion wave() innerhalb von openModal aufgerufen, die das Winken der Hand ausgelöst hat. Wenn wir eine Bibliothek bauen, müssen wir aufpassen, was wir hineinpacken. In diesem Fall würde es nicht viel Sinn machen, wave innerhalb von modal.js zu haben, da die meisten Modals keine winkende Hand brauchen.
Wir können für diesen Fall eine Callback-Funktion zur Verfügung stellen, die wave hin die Modal-Bibliothek injected. Nachdem die wave-Animation läuft, nachdem das Modal geöffnet wurde, nennen wir diesen Callback afterOpen.
User können die wave-Animation dann so übergeben:
Modal({ // ... afterOpen: wave;});Wir können die wave-Animation dann so aufrufen:
export default function Modal(settings) { // Declare variables
const modal = { open() { overlayElement.classList.add('is-open'); settings.afterOpen(); }, }
// Add event listeners}Wenn der User kein afterOpen Setting übergibt, kommt es zu einer Fehlermeldung. Wir können auf zwei Arten darauf reagieren:
- Mit einer Bedingung überprüfen
- eine Fallback-Funktion verwenden
Option 1 (Prüfen mit Bedingung) sieht so aus:
export default function Modal(settings) { // declare variable
const modal = { open() { overlayElement.classList.add('is-open'); if (settings.afterOpen) { settings.afterOpen(); } } };
// Add event listeners}Option 2 ist netter. Wir stellen eine leere Funktion als Fallback zur Verfügung. Das ist oft leichter mit einem default settings object. Wenn wir diese Komponente erzeugen, müssen wir das default settings object mit den Optionen, die der User übergeben hat, überschreiben.
// Default settings objectconst defaults = { buttonElement: '', modalElement: '', afterOpen() {}, // Empty function used as a fallback}
export default function Modal(settings) { // Overwrite default settings with user settings settings = Object.assign({}, defaults, settings);
// ...}Wir können nun afterOpen ohne Prüfung schreiben:
export default function Modal(settings) { // Overwrite settings // Declare variables
const modal = { open() { overlayElement.classList.add('is-open'; settings.afterOpen(); } };
// Add event listeners}Importing GSAP
Section titled “Importing GSAP”Die wave-Funktion ist von GSAP abhängig. Wir haben das ursprünglich mit einem Script-Tag eingebunden:
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>Nachdem wir alles in ES6-Module konvertiert haben, können wir GSAP direkt in main.js einbinden. Das macht es auch klarer, welche Bibliotheken wir in unseren Projekten inkludiert haben.
import 'https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js';Focusing on the first element
Section titled “Focusing on the first element”Wenn das Modal geöffnet wird, wollen wir das erste Element fokussieren. Dazu haben wir diesen Code geschrieben:
function openModal() { const input = modal.querySelector('input'); input.focus();}Nachdem wir nicht wissen, was der User in sein Modal hineinpackt, können wir nicht sicher sein, ob das erste Element immer input lautet. Wir müssen auch auf andere keyboard-fokussierbare Elemente prüfen.
Der einfachste Weg geht über eine Funktion wie in diesem Artikel beschrieben.
function getKeyboardFocusableElements(element = document) { return [ ...element.querySelectorAll( 'a, button, input, textarea, select, details, [tabindex]', ), ] .filter(el => !el.hasAttribute('disabled')) .filter( el => !el.hasAttribute('tabindex') || el.getAttribute('tabindex') >= 0, );}Wir erhalten alle keyboard-fokussierbaren Elemente, indem wir .modal__content in getKeyboardFocusableElements übergeben.
export default function Modal(settings) { // Overwrite settings // Declare variables const contentElement = modalElement.querySelector('.modal__content');
const modal = { open() { overlayElement.classList.add('is-open'); settings.afterOpen();
const focusableEls = getKeyboardFocusableElements(contentElement); if (focusableEls[0]) focusableEls[0].focus(); }, }
// Add event listeners}Trapping focus
Section titled “Trapping focus”Als nächstes wollen wir den Fokus innerhalb des Modals einfangen. Das war unser Code:
function openModal() { document.addEventListener('keydown', trapFocus); // ...}trapFocus sieht jetzt so aus:
function trapFocus(event) { const focusables = modal.querySelectorAll('input, button'); const firstFocusable = focusables[0]; const lastFocusable = focusables[focusables.length - 1];
// Directs to first focusable if ( document.activeElement === lastFocusable && event.key === 'Tab' && !event.shiftKey ) { event.preventDefault(); firstFocusable.focus(); }
// Directs to last focusable if ( document.activeElement === firstFocusable && event.key === 'Tab' && event.shiftKey ) { event.preventDefault(); lastFocusable.focus(); }}Wir müssen jetzt aber nach allen fokussierbaren Elementen suchen, nicht nur nach input und button. Also müssen wir getKeyboardFocusableElements in trapFocus hineinbringen.
Diesmal übergeben wir das Modal an getKeyboardFocusableElements weil wir den Close-Button mit einfügen wollen, und dieser ist ein Geschwisterelement von contentElement.
function trapFocus(event) { const focusables = getKeyboardFocusableElements(modalElement); const firstFocusable = focusables[0]; const lastFocusable = focusables[focusables.length - 1];
// ...}