Introduction
Moonroom est une petite exposition de véritables photographies des missions Apollo. La collection est prête, mais l'exposition actuelle ne contient aucune carte photo. Vous allez créer une carte porteuse de sens, l'étendre à partir des données, puis rendre toute la galerie lisible sur des écrans étroits et larges. Une Study collection distincte présente l'expérience de consultation attendue avant de commencer.
Vous devez connaître la structure HTML de base, les sélecteurs et la mise en page CSS, ainsi que les objets, tableaux et événements DOM JavaScript. Ce laboratoire présente figure, figcaption, les boutons natifs, les identifiants stables de photos, forEach, dataset et les règles adaptatives utilisées ici. La visionneuse détaillée est déjà préparée dans ce premier laboratoire ; vous construirez son comportement dans le suivant. L'auteur estime la durée à 50 minutes, lecture et erreurs de saisie courantes comprises.
Après avoir ouvert ou actualisé une page du site, attendez la fin de son chargement avant d'activer les commandes des photos. Utilisez WebIDE pour modifier /home/labex/project/moonroom et Web 8080 pour consulter le site. Dans ce laboratoire, modifiez uniquement index.html, gallery.js et styles.css. Conservez photos.js, viewer.js, images/ et credits.html. Les fichiers image et métadonnées fournis doivent rester associés à leurs identifiants d'origine. Tous les libellés du site restent en anglais. Ces vérifications pratiques au clavier ne constituent pas une certification complète d'accessibilité.
Associer une Photographie à Son Sens
Dans cette étape, utilisez une exposition fonctionnelle au clavier, puis créez la première carte de votre propre exposition.
Passez à Web 8080 et choisissez le lien de page Study collection. Il s'agit d'un exemple préparé séparément ; sa modification ne fait pas partie de votre espace de travail. Appuyez sur Tab jusqu'à ce que le premier bouton photo Earthrise présente un contour visible, puis sur Enter. La vue détaillée montre la Terre au-dessus de l'horizon lunaire. Appuyez sur Esc : le focus revient à la même carte. Remarquez que l'autre carte Earthrise présente une photographie et une légende différentes. Un titre est un libellé, pas une identité unique.
Choisissez le lien de page Current exhibition pour revenir à votre propre page. Ce lien navigue à l'intérieur du site ; il ne change pas d'onglet LabEx. Votre page contient initialement le titre et les crédits, mais aucune carte. C'est l'état incomplet que vous allez modifier.
Passez à WebIDE. Dans Explorer, ouvrez le dossier moonroom situé dans /home/labex/project. Si Explorer est masqué, choisissez View → Explorer. Ouvrez photos.js. Le tableau photos contient un objet par photographie. Chaque objet rassemble ces valeurs liées :
| Champ | Signification |
|---|---|
id |
Identité stable, comme earthrise-eight ; deux titres peuvent être identiques. |
thumbnail |
Une véritable version locale de petite taille utilisée dans une carte. |
large |
La version correspondante de plus grande taille pour la visionneuse. |
alt |
Un texte pertinent décrivant l'image pour les personnes qui ne peuvent pas la voir. |
title et caption |
Nom visible et contexte. |
Ne modifiez pas ce fichier. Pour le premier enregistrement, la miniature est images/earthrise-eight-thumb.jpg et la grande image est images/earthrise-eight.jpg. Un chemin d'image relatif est résolu à partir de la page HTML, pas du dossier courant de l'éditeur. La casse et l'extension .jpg sont importantes.
Un figure regroupe une image avec sa figcaption. Le bouton fait de l'ouverture de la photo une action native au clavier : il peut recevoir le focus avec Tab et s'activer avec Enter. Un div doté d'un gestionnaire de clic de souris ne fournirait pas automatiquement ces comportements. type="button" rend explicite le comportement voulu du bouton. Le titre et la légende visibles restent accessibles sans survol. aria-label donne au bouton son nom accessible court, Open Earthrise ; alt décrit le contenu de son image. Comme deux photographies partagent ce nom, aria-describedby relie également le bouton à sa légende via l'id unique de celle-ci. La légende apporte un contexte distinctif, comme Apollo 8 ou Apollo 11. Un nom accessible identifie une commande ; une description associée ajoute du contexte. Cela évite que deux commandes de même nom soient impossibles à distinguer pour une personne naviguant sans voir les images. Un espace réservé ou une image d'arrière-plan ne fournirait pas cette description de l'image.
L'attribut data-photo-card identifie le conteneur complet de la carte ; data-photo-id identifie son bouton d'ouverture. Tous deux utilisent l'identifiant stable de l'enregistrement. Nous conservons ces petits attributs de liaison pour que données, cartes et visionneuse désignent la même photo ; ce ne sont pas des textes visibles. Une carte peut comporter des éléments d'enveloppement supplémentaires, mais son bouton et sa légende doivent appartenir au conteneur le plus proche dont le data-photo-card correspond.
Ouvrez index.html, sélectionnez tout son texte avec Ctrl+A ou Cmd+A, puis remplacez-le par le HTML complet ci-dessous. Conservez les lignes script : defer exécute ces scripts après l'analyse du HTML, dans l'ordre indiqué. photos.js fournit les données, gallery.js construira la collection à l'étape suivante et le viewer.js préparé gère la vue détaillée de ce laboratoire. Le balisage dialog correspond au panneau que vous venez d'essayer ; son comportement sera expliqué et construit dans le laboratoire suivant.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Moonroom — A little farther from home</title>
<link rel="stylesheet" href="styles.css">
<script src="photos.js" defer></script>
<script src="gallery.js" defer></script>
<script src="viewer.js" defer></script>
</head>
<body>
<main>
<header>
<nav aria-label="Exhibitions"><a href="/">Current exhibition</a> · <a href="/example/">Study collection</a></nav>
<p class="eyebrow">Moonroom / Photographic studies</p>
<h1>A little farther from home.</h1>
<p class="intro">Three moments from the Apollo archive. Take a closer look.</p>
</header>
<section id="gallery" class="photo-grid" aria-label="Photographs">
<figure data-photo-card="earthrise-eight">
<button type="button" class="photo-button" data-photo-id="earthrise-eight" aria-label="Open Earthrise" aria-describedby="caption-earthrise-eight">
<img src="images/earthrise-eight-thumb.jpg" alt="Blue and white Earth above the grey lunar horizon">
<span class="photo-name">Earthrise</span>
</button>
<figcaption id="caption-earthrise-eight">Apollo 8 · Earth above the Moon, December 1968.</figcaption>
</figure>
</section>
<footer>Photographs: NASA. An independent educational exhibition. <a href="credits.html">Image credits</a></footer>
</main>
<dialog id="viewer" aria-labelledby="photo-title" aria-describedby="photo-caption">
<button id="close" type="button" autofocus>Close</button>
<h2 id="photo-title"></h2>
<img id="large-photo" alt="">
<p id="photo-error" role="status"></p>
<p id="photo-caption"></p>
<nav aria-label="Photo navigation">
<button id="previous" type="button">Previous</button>
<p id="photo-position"></p>
<button id="next" type="button">Next</button>
</nav>
</dialog>
</body>
</html>
Enregistrez avec Ctrl+S ou Cmd+S. Passez à Web 8080, choisissez Current exhibition si nécessaire, puis actualisez cette page du site. Vous devez voir une carte Earthrise, sa véritable image et la légende Apollo 8. La mise en page est encore simple ; vous la mettrez en forme plus tard. Utilisez Tab pour atteindre la carte, Enter pour l'ouvrir et Esc pour revenir. La grande image doit montrer la même scène avec davantage de détails.
Si l'image est cassée ou la page vide, revenez à WebIDE. Vérifiez que le fichier s'appelle index.html, qu'il est enregistré dans moonroom et que src, les guillemets et les balises de fermeture correspondent à l'exemple. Rouvrez Current exhibition et actualisez après l'enregistrement. Ne réinitialisez pas l'environnement et ne remplacez pas l'image par un autre fichier pour masquer un mauvais chemin. Vous pouvez restaurer tout le fichier en recopiant le HTML complet.
Lancez la vérification de cette étape après l'apparition de la véritable carte. Elle vérifie votre exposition actuelle, jamais l'exemple distinct.
Afficher Chaque Enregistrement sans Perdre Son Identité
Dans cette étape, remplacez la carte unique écrite à la main par des cartes générées à partir du tableau complet.
Restez dans WebIDE et rouvrez photos.js pour comparer. Le premier et le troisième enregistrements portent tous deux le nom Earthrise, mais leurs identifiants, chemins d'image et légendes diffèrent. Rechercher les photographies par leur titre ferait perdre cette distinction. La galerie terminée doit contenir exactement un bouton d'ouverture visible pour chacun des trois identifiants fournis, avec la miniature, le titre, le texte alternatif et la légende correspondants. Conservez l'ordre et les données fournis.
Ouvrez maintenant gallery.js. document.querySelector('#gallery') sélectionne l'élément dont l'id est gallery. replaceChildren() vide cet élément, supprimant la carte provisoire écrite à la main pour éviter de l'afficher deux fois. photos.forEach(function (photo) { ... }) appelle la fonction incluse une fois par enregistrement du tableau, dans l'ordre ; photo est l'objet courant. C'est la même idée de travail répété qu'une boucle, chaque enregistrement étant transmis à la fonction.
document.createElement() crée un véritable élément HTML en mémoire. Affecter textContent y place du texte au lieu de l'interpréter comme du HTML. append() insère les éléments préparés dans l'ordre indiqué. Le navigateur voit les mêmes relations que dans votre premier figure, bouton, image et légende.
dataset lit et écrit les attributs data-. JavaScript utilise la notation camel case : figure.dataset.photoCard écrit data-photo-card, et button.dataset.photoId écrit data-photo-id. Tous deux reçoivent photo.id, tandis que le titre affiché reçoit photo.title. Chaque légende reçoit aussi un id unique en joignant caption- à photo.id ; l'aria-describedby du bouton fait référence à cet identifiant de légende. La légende visible reste en dehors du bouton, mais lui est associée. setAttribute() définit un attribut comme aria-label ; l'opérateur + joint le texte Open au titre. Aucune syntaxe de gabarit de chaîne n'est nécessaire.
Remplacez tout le contenu de gallery.js par ce code complet, puis enregistrez :
const gallery = document.querySelector('#gallery');
gallery.replaceChildren();
photos.forEach(function (photo) {
const figure = document.createElement('figure');
figure.dataset.photoCard = photo.id;
const button = document.createElement('button');
button.type = 'button';
button.className = 'photo-button';
button.dataset.photoId = photo.id;
button.setAttribute('aria-label', 'Open ' + photo.title);
const image = document.createElement('img');
image.src = photo.thumbnail;
image.alt = photo.alt;
const title = document.createElement('span');
title.className = 'photo-name';
title.textContent = photo.title;
const caption = document.createElement('figcaption');
caption.id = 'caption-' + photo.id;
button.setAttribute('aria-describedby', caption.id);
caption.textContent = photo.caption;
button.append(image, title);
figure.append(button, caption);
gallery.append(figure);
});
Passez à Web 8080 et actualisez Current exhibition. Vous devez voir Earthrise, A trace of a visitor, puis le second Earthrise, dans cet ordre. Chaque légende reste visible sous son bouton. L'empreinte est une photographie au format portrait ; les images Earthrise sont carrées. La galerie encadrera bientôt leurs miniatures de manière uniforme, tout en préservant les différentes proportions dans la vue détaillée.
Utilisez Tab et Enter pour ouvrir chaque carte séparément. Le premier Earthrise a la légende Apollo 8 ; le dernier a celle d'Apollo 11. Appuyez sur Esc après chaque ouverture et vérifiez que le focus revient à la carte utilisée, y compris la dernière plutôt que toujours la première.
S'il ne reste qu'une carte, le script n'est peut-être pas enregistré, contient une erreur de syntaxe ou se trouve dans le mauvais dossier. Revenez à WebIDE, comparez gallery.js au bloc complet et vérifiez le }); final de forEach. Ne modifiez pas photos.js pour rendre ses enregistrements semblables. Enregistrez, actualisez le site et comparez de nouveau les cartes avant de lancer la vérification.
Adapter la Galerie et Afficher le Focus du Clavier
Dans cette étape, rendez l'exposition complète lisible sur un écran étroit et facilitez le repérage de la carte qui a le focus.
Revenez à WebIDE et ouvrez styles.css. Une mise en page adaptative s'ajuste à l'espace disponible au lieu d'imposer une largeur de page fixe. Cette feuille de style utilise flexbox, déjà rencontré dans les bases de CSS. display: flex place les cartes en ligne ; flex-wrap: wrap autorise le passage à la ligne ; gap laisse de l'espace entre les cartes. flex: 1 1 260px permet à un figure de grandir, de rétrécir et de partir d'une largeur préférée de 260 pixels. min-width: 0 permet au contenu long de rétrécir dans une carte au lieu d'élargir la page.
L'image de carte utilise width: 100% et aspect-ratio: 4 / 3 pour obtenir un cadre uniforme. object-fit: cover remplit ce cadre de miniature en rognant ses bords affichés ; il ne modifie pas le fichier. À l'inverse, l'image détaillée utilise object-fit: contain, max-width et max-height pour rester entièrement visible avec ses propres proportions. vw et vh sont des centièmes de la largeur et de la hauteur de la zone d'affichage. min() limite une taille, tandis que clamp() permet au titre de grandir entre un minimum et un maximum. overflow-wrap: anywhere autorise le retour à la ligne d'un titre très long au lieu d'élargir la page.
:focus-visible sélectionne une commande lorsque le focus du clavier doit être affiché. Un contour très contrasté et outline-offset rendent le bouton courant visible sans déplacer la mise en page. Ne supprimez pas le contour du navigateur sans fournir un autre indicateur clair. Ce projet accepte un contour de focus distinct d'au moins 2 pixels, une bordure visible distincte d'au moins 2 pixels, ou une ombre visible apparaissant lorsque la commande reçoit le focus au clavier. Vous pouvez changer les couleurs ou la mise en page en conservant ces comportements observables.
Les règles du bas préservent la visionneuse préparée : ::backdrop stylise le fond derrière une boîte de dialogue modale, :disabled distingue visuellement un bouton de limite et [hidden] doit réellement masquer une image en échec, même si les images ordinaires de la visionneuse utilisent display: block. La requête média de réduction des animations respecte la préférence de mouvement du navigateur ; cette exposition n'a aucune transition animée.
Remplacez tout le contenu de styles.css par la feuille de style complète suivante et enregistrez :
* { box-sizing: border-box; }
body { margin: 0; background: #f5f1e8; color: #252c28; font: 17px/1.6 system-ui, sans-serif; }
main { max-width: 1160px; margin: auto; padding: 40px 24px; }
header { max-width: 760px; margin-bottom: 40px; }
.eyebrow { color: #6e593f; font-size: 12px; letter-spacing: .16em; text-transform: uppercase; }
h1 { margin: 12px 0; font: clamp(2.5rem, 6vw, 5rem)/1.08 Georgia, serif; }
.intro { max-width: 40rem; color: #5c655e; }
.photo-grid { display: flex; flex-wrap: wrap; gap: 28px; }
figure { flex: 1 1 260px; min-width: 0; margin: 0; }
button { color: inherit; font: inherit; cursor: pointer; }
.photo-button { width: 100%; padding: 0; text-align: left; background: transparent; border: 0; }
.photo-button img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: 4px; }
.photo-name { display: block; margin-top: 12px; font: 1.5rem/1.3 Georgia, serif; overflow-wrap: anywhere; }
figcaption { margin-top: 6px; color: #5c655e; font-size: 14px; }
a { color: #814820; text-underline-offset: 4px; }
button:focus-visible, a:focus-visible { outline: 3px solid #af421f; outline-offset: 5px; }
footer { margin-top: 48px; border-top: 1px solid #d1c8b8; padding-top: 18px; color: #5c655e; font-size: 13px; }
dialog { width: min(880px, 94vw); max-height: 94vh; overflow: auto; padding: 24px; border: 0; border-radius: 8px; background: #fffcf6; color: #252c28; }
dialog::backdrop { background: #19251fe6; }
dialog h2 { font: 1.8rem/1.3 Georgia, serif; overflow-wrap: anywhere; }
dialog img { display: block; max-width: 100%; max-height: 52vh; margin: auto; object-fit: contain; }
dialog img[hidden] { display: none; }
dialog button { padding: 8px 14px; border: 1px solid #6c746b; border-radius: 4px; background: #fffcf6; }
dialog nav { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
dialog button:disabled { opacity: .45; cursor: default; }
#photo-position { white-space: nowrap; }
#photo-error { color: #9b301c; }
#photo-error:empty { display: none; }
@media (prefers-reduced-motion: reduce) { * { scroll-behavior: auto; } }
Passez à Web 8080 et actualisez Current exhibition. Dans une zone d'affichage large, les cartes partagent une ligne ; dans une zone étroite, elles s'empilent en une colonne lisible. Redimensionnez le navigateur ou le panneau du site à environ 390 pixels de large, puis élargissez-le de nouveau. Il ne doit y avoir ni défilement horizontal de la page, ni titres coupés, ni carte manquante. La véritable grande photographie doit conserver son format portrait ou carré à l'ouverture.
Depuis la page, appuyez plusieurs fois sur Tab. Les liens du site et les trois boutons photo doivent afficher un contour de focus visible. Enter ouvre la photo sélectionnée ; Esc y revient. Aucune description ne doit exiger un survol de la souris. Ouvrez Image credits pour vérifier que sa véritable page répertorie les sources NASA des images, puis choisissez Current exhibition pour revenir. Ce sont des liens du site, distincts du passage entre WebIDE et Web 8080.
Si la mise en page reste simple, vérifiez que le fichier enregistré s'appelle styles.css et que index.html le référence toujours. Si une carte dépasse la largeur de la page, vérifiez min-width: 0, width: 100% sur son image et les règles de retour à la ligne flex. Restaurez la feuille de style complète si nécessaire ; conservez chaque photo au lieu d'en masquer une difficile. Lancez la vérification finale après avoir examiné les deux tailles d'écran et le parcours au clavier.

Dans cette vue finale sur écran large, comparez les légendes du premier et du dernier Earthrise : les photographies viennent de missions différentes. L'empreinte reste une carte centrale distincte. Les mêmes trois cartes s'empilent sur un écran étroit.
Résumé
Vous avez relié une véritable image à sa description, transformé une carte sémantique en une collection complète générée à partir des données et distingué deux photographies de même titre grâce à des identifiants stables. Votre galerie adaptative conserve toutes les cartes, le contenu réel des images et un focus du clavier visible. Dans le laboratoire suivant, vous construirez vous-même la visionneuse détaillée, avec des limites de navigation et un échec d'image récupérable.
