Une galerie photo est le projet idéal pour faire travailler ensemble HTML, CSS et JavaScript : une grille qui s'adapte à toutes les tailles d'écran, puis une visionneuse qui affiche les photos en grand, avec les flèches du clavier. Tu vas utiliser deux outils modernes qui simplifient énormément le travail : CSS Grid et l'élément <dialog>. Durée : environ 45 minutes.
Ce que tu vas construire
- une grille de miniatures qui passe automatiquement de 1 à 4 colonnes selon la largeur de l'écran ;
- une visionneuse plein écran qui s'ouvre au clic sur une photo ;
- la navigation avec les boutons ou les flèches du clavier, en boucle ;
- la fermeture avec la croix, la touche Échap ou un clic sur le fond ;
- une galerie qui fonctionne même si le JavaScript ne se charge pas.
Crée un dossier avec index.html, style.css, script.js et un sous-dossier images contenant six photos (photo-1.jpg à photo-6.jpg).
1. La structure HTML
Chaque miniature est une image dans un lien qui pointe vers la photo en grand. C'est ce qu'on appelle l'amélioration progressive : sans JavaScript, un clic ouvre simplement la photo ; avec JavaScript, on intercepte le clic pour l'afficher dans la visionneuse.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ma galerie</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<h1>Mes photos de vacances</h1>
<ul class="gallery">
<li><a href="images/photo-1.jpg"><img src="images/photo-1.jpg" alt="Coucher de soleil sur la mer" loading="lazy"></a></li>
<li><a href="images/photo-2.jpg"><img src="images/photo-2.jpg" alt="Sentier dans la forêt" loading="lazy"></a></li>
<li><a href="images/photo-3.jpg"><img src="images/photo-3.jpg" alt="Village dans la montagne" loading="lazy"></a></li>
<li><a href="images/photo-4.jpg"><img src="images/photo-4.jpg" alt="Port de pêche au petit matin" loading="lazy"></a></li>
<li><a href="images/photo-5.jpg"><img src="images/photo-5.jpg" alt="Champ de lavande" loading="lazy"></a></li>
<li><a href="images/photo-6.jpg"><img src="images/photo-6.jpg" alt="Lac entouré de sommets" loading="lazy"></a></li>
</ul>
</main>
<dialog class="lightbox" aria-label="Photo agrandie">
<figure>
<img src="" alt="">
<figcaption></figcaption>
</figure>
<button class="prev" aria-label="Photo précédente">‹</button>
<button class="next" aria-label="Photo suivante">›</button>
<button class="close" aria-label="Fermer">×</button>
</dialog>
<script src="script.js"></script>
</body>
</html>
Trois détails qui comptent :
altdécrit chaque photo : il est lu par les lecteurs d'écran et on le réutilise comme légende ;loading="lazy"ne charge une image que lorsqu'elle approche de l'écran : la page s'affiche plus vite ;<dialog>est une fenêtre modale native : il gère tout seul l'affichage au premier plan, le fond assombri, la touche Échap et le focus clavier.
2. La grille en CSS
body {
margin: 0;
font-family: system-ui, sans-serif;
}
main {
max-width: 72rem;
margin: 0 auto;
padding: 1rem;
}
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
gap: 1rem;
padding: 0;
list-style: none;
}
.gallery img {
display: block;
width: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
border-radius: 0.5rem;
transition: transform 0.2s;
}
.gallery a:hover img,
.gallery a:focus-visible img {
transform: scale(1.03);
}
.lightbox {
max-width: 90vw;
padding: 0;
border: 0;
background: transparent;
}
.lightbox::backdrop {
background: rgb(0 0 0 / 0.85);
}
.lightbox img {
display: block;
max-width: 90vw;
max-height: 80vh;
}
.lightbox figcaption {
padding: 0.5rem 0;
color: #fff;
text-align: center;
}
.lightbox button {
position: fixed;
top: 50%;
border: 0;
background: none;
color: #fff;
font-size: 3rem;
cursor: pointer;
}
.lightbox .prev { left: 1rem; }
.lightbox .next { right: 1rem; }
.lightbox .close { top: 1rem; right: 1rem; }
La ligne clé est grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)) : « crée autant de colonnes d'au moins 14rem que la place le permet, et partage l'espace restant ». Résultat : une grille responsive sans aucune media query. aspect-ratio et object-fit: cover donnent la même forme à toutes les miniatures, même si les photos ont des formats différents.
3. La visionneuse en JavaScript
const links = [...document.querySelectorAll('.gallery a')];
const lightbox = document.querySelector('.lightbox');
const image = lightbox.querySelector('img');
const caption = lightbox.querySelector('figcaption');
let current = 0;
// Affiche la photo n° index ; le modulo fait boucler de la dernière à la première.
function show(index) {
current = (index + links.length) % links.length;
const thumbnail = links[current].querySelector('img');
image.src = links[current].href;
image.alt = thumbnail.alt;
caption.textContent = thumbnail.alt;
}
links.forEach((link, index) => {
link.addEventListener('click', (event) => {
event.preventDefault(); // Sans JavaScript, le lien ouvre simplement l'image.
show(index);
lightbox.showModal();
});
});
lightbox.querySelector('.prev').addEventListener('click', () => show(current - 1));
lightbox.querySelector('.next').addEventListener('click', () => show(current + 1));
lightbox.querySelector('.close').addEventListener('click', () => lightbox.close());
// Flèches du clavier ; la touche Échap ferme déjà un <dialog> toute seule.
lightbox.addEventListener('keydown', (event) => {
if (event.key === 'ArrowLeft') show(current - 1);
if (event.key === 'ArrowRight') show(current + 1);
});
// Un clic sur le fond sombre (en dehors de la photo) ferme aussi la visionneuse.
lightbox.addEventListener('click', (event) => {
if (event.target === lightbox) lightbox.close();
});
Le cœur du script est la fonction show(index). Le calcul (index + links.length) % links.length fait boucler la navigation : après la dernière photo, on revient à la première, et avant la première, on passe à la dernière.
event.preventDefault() empêche le lien d'ouvrir l'image dans l'onglet, puisque la visionneuse prend le relais. Et showModal() ouvre le <dialog> en mode modal, avec le fond ::backdrop stylé dans le CSS.
4. Tester ta galerie
- Redimensionne la fenêtre : le nombre de colonnes doit changer tout seul.
- Clique sur une photo, puis utilise les flèches du clavier : la légende doit suivre.
- Appuie sur Échap ou clique sur le fond : la visionneuse se ferme.
- Désactive le JavaScript dans les outils de développement : les liens doivent toujours ouvrir les photos.
Pour aller plus loin
- Des miniatures légères : génère une petite version de chaque photo pour la grille (
photo-1-small.jpg) et garde la grande pour la visionneuse. Ta page sera beaucoup plus rapide. - Le balayage sur mobile : écoute les évènements
pointerdownetpointeruppour passer à la photo suivante d'un glissement du doigt. - Des filtres : ajoute des boutons « Mer », « Montagne »… qui masquent les photos d'une autre catégorie grâce à un attribut
data-categorie.
Ta galerie est en ligne ? Partage-la dans la vitrine des lecteurs.