Eine Fotodetailansicht mit Tastaturbedienung Erstellen

JavaScriptBeginner
Jetzt üben

Einführung

Moonrooms echte Fotokarten haben nun lesbare Bildunterschriften und ein responsives Layout. Die Detailansicht ist noch nicht verbunden. Deine Aufgabe ist, über jede Karte ihr eigenes größeres Foto zu öffnen, Besuchern das Durchblättern der Sammlung zu ermöglichen und sie genau zur ursprünglich verwendeten Karte zurückzuführen. Außerdem erzeugst und behebst du einen echten Fehler durch ein fehlendes Bild, ohne es durch ein anderes zu ersetzen.

Dieses Lab beginnt mit seinem eigenen vollständigen Raster und lokalen Fotos. Es hängt nicht von Dateien einer früheren Umgebung ab. Du benötigst die Karten-Daten-Beziehungen aus dem ersten Lab sowie JavaScript-Funktionen, DOM-Auswahl und Ereignisse. Neue Verhaltensweisen für Array-Suche, Dialog, Fokus und Bildereignisse werden vor ihrer Verwendung eingeführt. Lesen und Üben dauern nach Schätzung des Autors 60 Minuten.

Warte nach dem Öffnen oder Neuladen einer Website-Seite, bis sie vollständig geladen ist, bevor du die Fotosteuerung bedienst. Verwende WebIDE für /home/labex/project/moonroom und Web 8080 für die Website. Bearbeite viewer.js; die vorübergehende Änderung des Pfads large in photos.js ist nur für die ausdrücklich erklärte Fehlerübung vorgesehen und muss vor der Prüfung rückgängig gemacht werden. Erhalte die übrigen bereitgestellten Dateien, Metadaten und Originalbilder. Die zwei Earthrise-Datensätze bleiben getrennt, und jede Karte, Miniatur und Bildunterschrift muss nutzbar bleiben. Gleichwertiges JavaScript-Verhalten ist erlaubt; behalte die vorgegebenen Schaltflächen-IDs, stabilen Foto-Verknüpfungen, sichtbaren Bedienelemente Close, Previous und Next sowie die Titel- und Beschreibungszuordnungen des nativen Dialogs bei. Diese Tastaturübungen sind keine vollständige Zertifizierung der Barrierefreiheit.

Das Richtige Foto Öffnen und zu Seiner Karte Zurückkehren

In diesem Schritt verbindest du die vorhandenen Karten mit einem nativen modalen Dialog einschließlich eines zuverlässigen Rückkehrpunkts für den Tastaturfokus.

Wechsle zu Web 8080, wähle den Website-Link Study collection und öffne und schließe ein Foto mit Tab, Enter und Esc. Wähle danach Current exhibition. Die drei Karten und Bildunterschriften sind bereits vorhanden. Probiere Enter auf einer Karte: Es hat noch keine Wirkung, da dein viewer.js nur einen Kommentar enthält. Das ist der erwartete unvollständige Zustand, kein Umgebungsfehler.

Wechsle zu WebIDE, öffne den Ordner moonroom im Explorer und lies index.html. Das Element dialog ist ein integriertes Browserfenster. Sein aria-labelledby="photo-title" verweist auf die sichtbare Überschrift, aria-describedby="photo-caption" auf die aktuelle Erklärung. showModal() öffnet es modal: Andere Seiteninhalte werden währenddessen inaktiv. Der Browser übernimmt das grundlegende modale Tab-Verhalten und das Schließen mit Esc; du brauchst keine eigene Fokusfalle zu schreiben. Füge dem Dialog selbst kein tabindex hinzu. Die vorbereitete Schaltfläche Close hat autofocus, sodass beim Öffnen eine eindeutige Möglichkeit zum Verlassen den Fokus erhält.

Öffne viewer.js. const hält Referenzen auf DOM-Elemente. let wird für Werte verwendet, die wir ändern: currentIndex ist die gewählte Array-Position, beginnend bei null, und openingButton merkt sich die tatsächliche Schaltfläche, die das Fenster geöffnet hat. photos[currentIndex] ruft den vollständigen Datensatz dieses Fotos ab. showPhoto() aktualisiert Titel, Bildunterschrift, Alternativtext und echtes größeres Bild gemeinsam.

Das Raster hat einen einzigen Klick-Listener. Enter auf einer nativen Schaltfläche aktiviert ihn ebenfalls. event.target ist das genau angeklickte Element, etwa das Bild oder der Titel in einer Schaltfläche. closest('button[data-photo-id]') sucht nach außen nach der passenden Schaltfläche zum Öffnen. if (!button) return ignoriert Klicks, die nicht von einer solchen Schaltfläche stammen. Das ist Ereignisdelegation: Ein Listener behandelt alle vorhandenen Kartenschaltflächen.

findIndex() durchsucht das Array und liefert die Position, deren ID mit dataset.photoId der Schaltfläche übereinstimmt. Die Callback-Funktion gibt true zurück, wenn photo.id === button.dataset.photoId gilt; === vergleicht ohne Typumwandlung der Werte. Wenn kein Datensatz passt, gibt findIndex() -1 zurück. Die Schutzabfrage bricht dann ab, statt ein falsches Foto zu öffnen. Wir vergleichen bewusst IDs und nicht den wiederholten Titel Earthrise.

Das Ereignis close des Dialogs wird sowohl beim Schließen mit Esc als auch mit Close ausgeführt. openingButton.focus() setzt den Fokus auf die gemerkte Karte zurück. Diese Verwendung von focus() implementiert das Rückkehrverhalten; beim manuellen Test musst du die Karten trotzdem mit der echten Tastatur erreichen und aktivieren.

Ersetze den gesamten Inhalt von viewer.js durch den vollständigen Code unten und speichere mit Ctrl+S oder Cmd+S:

const viewer = document.querySelector('#viewer');
const largePhoto = document.querySelector('#large-photo');
const photoTitle = document.querySelector('#photo-title');
const photoCaption = document.querySelector('#photo-caption');
let currentIndex = 0;
let openingButton;

function showPhoto() {
  const photo = photos[currentIndex];
  photoTitle.textContent = photo.title;
  photoCaption.textContent = photo.caption;
  largePhoto.alt = photo.alt;
  largePhoto.src = photo.large;
}

document.querySelector('#gallery').addEventListener('click', function (event) {
  const button = event.target.closest('button[data-photo-id]');
  if (!button) return;
  currentIndex = photos.findIndex(function (photo) {
    return photo.id === button.dataset.photoId;
  });
  if (currentIndex === -1) return;
  openingButton = button;
  showPhoto();
  viewer.showModal();
});

document.querySelector('#close').addEventListener('click', function () {
  viewer.close();
});
viewer.addEventListener('close', function () {
  openingButton.focus();
});

Wechsle zu Web 8080, wähle gegebenenfalls Current exhibition und lade die Seite neu. Erreiche jede Karte mit Tab und öffne sie mit Enter. Der erste Earthrise muss das Apollo-8-Foto und seine Bildunterschrift zeigen, der letzte Apollo 11. Drücke jeweils Esc und beobachte den Fokus auf derselben Karte. Öffne ein Foto erneut und verwende Tab und dann Enter auf Close als zweiten Schließweg. Previous und Next werden erst im nächsten Schritt verbunden.

Wenn sich kein Fenster öffnet, kehre zu WebIDE zurück und vergleiche die Datei mit dem vollständigen Block. Prüfe den Dateinamen viewer.js, die Skriptzeile in index.html und ob die Werte von data-photo-id noch zu photos.js passen. Speichere nach der Korrektur eines Tippfehlers und lade die Website neu. Erscheint die falsche Szene, vergleiche large, Titel und Bildunterschrift des aktuellen Datensatzes gemeinsam; ändere die Quelldaten nicht, um einen Fehler bei der ID-Suche zu verdecken. Starte die Prüfung erst, wenn alle drei unabhängigen Enter/Esc-Wege funktionieren.

Die Gesamte Sammlung und Ihre Grenzen Durchlaufen

In diesem Schritt aktualisierst du dieselbe Detailansicht, ohne die ursprünglich öffnende Karte zu verlieren.

Kehre zu WebIDE zurück und vergleiche die bei null beginnenden Array-Positionen in photos.js: Die erste ist 0, die zweite 1, die letzte photos.length - 1. length zählt die Datensätze. Eine sichtbare Positionsangabe soll dagegen bei eins beginnen, sodass currentIndex + 1 eine verständliche Zahl wie 1 / 3 liefert.

Die Eigenschaft disabled nimmt einen booleschen Wert an. previous.disabled = currentIndex === 0 deaktiviert Previous nur beim ersten Foto; die entsprechende Next-Regel deaktiviert dieses nur beim letzten. Diese Grenzen verhindern Zugriffe außerhalb des Arrays. Die Klick-Handler prüfen ebenfalls die Grenzen, bevor sie den Index ändern. currentIndex -= 1 subtrahiert eins und speichert den neuen Index; += 1 addiert eins. Jede Änderung ruft showPhoto() auf, damit Bild, Beschreibung und Position synchron bleiben.

Nur das Öffnen einer Karte ändert openingButton. Das Navigieren zu einem anderen Foto innerhalb des Dialogs darf diesen Wert nicht ändern: Beim Schließen geht es weiterhin zur Karte zurück, an der der Besuch begann, selbst wenn gerade ein anderes Foto angezeigt wird.

Ersetze den gesamten Inhalt von viewer.js durch diese vollständige Version und speichere. Sie erhält Öffnen und Schließen und ergänzt die Navigation mit Grenzen:

const viewer = document.querySelector('#viewer');
const largePhoto = document.querySelector('#large-photo');
const photoTitle = document.querySelector('#photo-title');
const photoCaption = document.querySelector('#photo-caption');
const previous = document.querySelector('#previous');
const next = document.querySelector('#next');
let currentIndex = 0;
let openingButton;

function showPhoto() {
  const photo = photos[currentIndex];
  photoTitle.textContent = photo.title;
  photoCaption.textContent = photo.caption;
  largePhoto.alt = photo.alt;
  largePhoto.src = photo.large;
  previous.disabled = currentIndex === 0;
  next.disabled = currentIndex === photos.length - 1;
  document.querySelector('#photo-position').textContent = (currentIndex + 1) + ' / ' + photos.length;
}

document.querySelector('#gallery').addEventListener('click', function (event) {
  const button = event.target.closest('button[data-photo-id]');
  if (!button) return;
  currentIndex = photos.findIndex(function (photo) {
    return photo.id === button.dataset.photoId;
  });
  if (currentIndex === -1) return;
  openingButton = button;
  showPhoto();
  viewer.showModal();
});

previous.addEventListener('click', function () {
  if (currentIndex > 0) {
    currentIndex -= 1;
    showPhoto();
  }
});
next.addEventListener('click', function () {
  if (currentIndex < photos.length - 1) {
    currentIndex += 1;
    showPhoto();
  }
});
document.querySelector('#close').addEventListener('click', function () {
  viewer.close();
});
viewer.addEventListener('close', function () {
  openingButton.focus();
});

Wechsle zu Web 8080 und lade Current exhibition neu. Öffne den ersten Earthrise mit der Tastatur. Previous ist deaktiviert; Tab überspringt diese Schaltfläche. Gehe mit Tab zu Next und drücke Enter. Du solltest den Stiefelabdruck im Hochformat, seinen Titel und seine Bildunterschrift sowie 2 / 3 sehen. Gehe weiter zum letzten Earthrise und prüfe, dass sein Apollo-11-Bild und seine Bildunterschrift erscheinen, während Next deaktiviert ist.

Gehe mit Tab zu Previous und durch beide Bilder zurück. Halte beim Drücken von Tab die Umschalttaste Shift gedrückt, wenn du rückwärts durch die verfügbaren Bedienelemente gehen willst; dies bewegt den Tastaturfokus, während das Aktivieren von Previous das Foto wechselt. Das sind unterschiedliche Aktionen. Drücke Esc und prüfe, dass der Fokus zur ursprünglichen ersten Karte zurückkehrt. Öffne nun die mittlere Karte direkt, navigiere woandershin und schließe: Der Fokus muss zur mittleren Karte zurückkehren.

Eine deaktivierte Grenze beim ersten oder letzten Foto ist erwartet. Ein aktiv wirkendes Bedienelement ohne Wirkung ist es nicht. Wenn sich ein Titel, aber nicht das Foto ändert, kehre zu WebIDE zurück und stelle sicher, dass jede Bewegung dieselbe Funktion showPhoto() aufruft. Wenn Esc immer zur ersten Karte zurückkehrt, prüfe, ob openingButton im Listener zum Öffnen der Karte zugewiesen und nicht in den Navigations-Handlern überschrieben wird. Stelle bei Bedarf den vollständigen Code wieder her, speichere und lade vor der Prüfung neu.

Ein Fehlendes Bild Ehrlich Melden und die Fortsetzung Ermöglichen

In diesem Schritt behandelst du einen echten Bildladefehler und erhältst dabei die Position des Besuchers und die Nutzbarkeit der übrigen Sammlung.

Das Ereignis load eines Bildes bedeutet, dass seine Datei erfolgreich geladen wurde; error bedeutet, dass das Bild nicht geladen werden konnte, etwa weil sein Pfad fehlt. Dies sind Bildereignisse des Browsers, kein Beweis dafür, dass eine beliebige HTTP-Antwort ein gültiges Bild enthält. Ein Verbindungsfehler zum Server kann die gesamte Seite unerreichbar machen; hier funktioniert die Seite, während eine einzelne Bildanfrage scheitert.

Öffne viewer.js in WebIDE. Diese Version setzt die Ereignis-Handler vor der Zuweisung von src, damit ein schnelles Bild aus dem Cache nicht schon vor dem Vorhandensein seiner Handler fertig geladen ist. Sie blendet das alte Bild beim Laden aus und zeigt Loading photo... im vorhandenen Absatz mit role="status". Bei Erfolg zeigt sie das echte Bild und leert die Meldung. Bei einem Fehler bleibt das Bild verborgen, und eine lesbare Anleitung zur Behebung erscheint. Die CSS-Regel dialog img[hidden] { display: none; } ist erforderlich, weil die gewöhnliche Regel für Dialogbilder display: block verwendet. Andernfalls würde diese CSS-Regel ein einzelnes hidden-Attribut übersteuern.

Der Fehler-Handler ändert src nicht zu einer Miniatur oder einem anderen Foto. Das würde den Fehler verbergen und Inhalte zeigen, die nicht zum aktuellen Datensatz passen. Der Nutzer kann das Fenster weiterhin schließen oder zu einem intakten Foto navigieren.

Ersetze den gesamten Inhalt von viewer.js durch die vollständige Endversion und speichere:

const viewer = document.querySelector('#viewer');
const largePhoto = document.querySelector('#large-photo');
const photoTitle = document.querySelector('#photo-title');
const photoCaption = document.querySelector('#photo-caption');
const photoError = document.querySelector('#photo-error');
const previous = document.querySelector('#previous');
const next = document.querySelector('#next');
let currentIndex = 0;
let openingButton;

function showPhoto() {
  const photo = photos[currentIndex];
  photoTitle.textContent = photo.title;
  photoCaption.textContent = photo.caption;
  largePhoto.alt = photo.alt;
  photoError.textContent = 'Loading photo...';
  largePhoto.hidden = true;
  largePhoto.onload = function () {
    largePhoto.hidden = false;
    photoError.textContent = '';
  };
  largePhoto.onerror = function () {
    largePhoto.hidden = true;
    photoError.textContent = 'This photo could not be loaded. Close the viewer and check its large image path.';
  };
  largePhoto.src = photo.large;
  previous.disabled = currentIndex === 0;
  next.disabled = currentIndex === photos.length - 1;
  document.querySelector('#photo-position').textContent = (currentIndex + 1) + ' / ' + photos.length;
}

document.querySelector('#gallery').addEventListener('click', function (event) {
  const button = event.target.closest('button[data-photo-id]');
  if (!button) return;
  currentIndex = photos.findIndex(function (photo) {
    return photo.id === button.dataset.photoId;
  });
  if (currentIndex === -1) return;
  openingButton = button;
  showPhoto();
  viewer.showModal();
});

previous.addEventListener('click', function () {
  if (currentIndex > 0) {
    currentIndex -= 1;
    showPhoto();
  }
});
next.addEventListener('click', function () {
  if (currentIndex < photos.length - 1) {
    currentIndex += 1;
    showPhoto();
  }
});
document.querySelector('#close').addEventListener('click', function () {
  viewer.close();
});
viewer.addEventListener('close', function () {
  openingButton.focus();
});

Erzeuge nun einen kontrollierten Pfadfehler in deinen eigenen Dateien und behebe ihn wieder. Öffne photos.js in WebIDE. Ändere im Datensatz mit der id footprint nur den Wert large von images/footprint.jpg zu images/missing-photo.jpg. Verändere weder Miniatur noch ID, Bildunterschrift oder Alternativtext. Speichere die Datei.

Wechsle zu Web 8080, lade Current exhibition neu und öffne A trace of a visitor mit Tab und Enter. Die Miniatur funktioniert weiterhin, aber die Anfrage nach dem größeren Bild schlägt nun fehl. Erwartet wird der sichtbare Text This photo could not be loaded. Close the viewer and check its large image path. Das Fenster darf weder Earthrise noch eine gestreckte Miniatur als Ersatz anzeigen. Titel und Bildunterschrift des Stiefelabdrucks bleiben erhalten, und Close, Previous und Next bleiben verfügbar.

Das fehlende größere Foto hinterlässt eine lesbare Fehlermeldung und nutzbare Navigation.

Das Fenster mit dem Ladefehler behält Titel und Bildunterschrift des Stiefelabdrucks bei, ohne Ersatzbild. Next bleibt verfügbar, damit der Besucher fortfahren kann.

Verwende Tab und Enter auf Next. Das intakte Apollo-11-Bild sollte laden und die Fehlermeldung verschwinden. Drücke Esc und prüfe, ob der Fokus zur Karte mit dem Stiefelabdruck zurückkehrt, die das Fenster ursprünglich geöffnet hat. So wird das Durchsehen der Sammlung fortgesetzt; der falsche Dateipfad wurde dadurch nicht repariert.

Kehre zu WebIDE zurück, stelle den exakten Wert large des footprint-Datensatzes auf images/footprint.jpg zurück und speichere. Wechsle zu Web 8080 und lade Current exhibition neu, damit der Browser die wiederhergestellten Daten liest. Öffne den Stiefelabdruck erneut. Sein echtes Hochformatbild muss nun dekodiert und ohne Fehlermeldung angezeigt werden. Das Neuladen des Browsers lädt die Seitendaten erneut; es setzt weder deine gespeicherte Arbeit zurück noch erstellt es die Umgebung neu.

Der wiederhergestellte Pfad lädt wieder das echte Hochformatfoto.

Nach Wiederherstellung des Pfads und Neuladen füllt der Stiefelabdruck im Hochformat die Detailansicht ohne Verzerrung; die Fehlermeldung ist verschwunden.

Prüfe abschließend alle drei Karten, die Vorwärts- und Rückwärtsgrenzen sowie beide Schließwege erneut. Verkleinere die Breite des Website-Panels und prüfe, ob das gesamte Foto und die umgebrochene Überschrift ohne horizontales Scrollen der Seite in den Dialog passen. Alle ursprünglichen Miniaturen, Metadaten, Bilder und Bildnachweise müssen erhalten bleiben. Die abschließende Prüfung verlangt die Wiederherstellung der bereitgestellten Daten und testet den Fehlerfall nur in einer Wegwerfkopie deiner Dateien; sie löscht niemals ein Bild in deiner tatsächlichen Ausstellung.

Zusammenfassung

Du hast stabile IDs verwendet, um den echten Inhalt jedes Fotos in einem nativen Dialog anzuzeigen. Du hast begrenzte Vorwärts-/Rückwärtsnavigation ergänzt, den ursprünglichen Rückkehrpunkt für Esc und Close erhalten und eine echte Anfrage nach einem fehlenden Bild behandelt, ohne ein anderes Foto einzusetzen. Deine Galerie hält Raster, Bildunterschriften und andere Fotografien nutzbar, während sie einen lokalen Fehler ehrlich meldet.