Développement Web

Tutoriel : Créer une galerie photo responsive avec CSS Grid

14 min de lecture

Prérequis

  • Niveau : bases HTML/CSS et CSS Grid (cf. CSS Grid Layout).
  • Outils : VS Code + Live Server, navigateur moderne avec DevTools mobile.
  • Temps estimé : 1 h 30.

Pourquoi CSS Grid pour une galerie ?

Avant Grid, créer une galerie d’images responsive demandait des hacks à base de float, inline-block et calculs de pourcentages. Aujourd’hui, repeat(auto-fill, minmax(...)) donne une grille fluide en une seule ligne. Combiné à object-fit et aspect-ratio, vous obtenez une galerie pro en 20 lignes de CSS.

CSS Grid : l’outil parfait pour les galeries

CSS Grid permet de créer des galeries photo dynamiques et responsives en quelques lignes de code. Fini les calculs de largeur compliqués avec les float !

Galerie basique : grille uniforme

Voici la mise en pratique pour Galerie basique : grille uniforme. Le bloc ci-dessous est copiable directement dans votre projet, lisible ligne par ligne. Lisez-le une première fois en survol pour repérer la structure générale, puis adaptez les noms de variables, identifiants et valeurs à votre contexte avant de l’exécuter en local.

<div class="galerie">
    <img src="photo1.jpg" alt="Description 1">
    <img src="photo2.jpg" alt="Description 2">
    <img src="photo3.jpg" alt="Description 3">
    <img src="photo4.jpg" alt="Description 4">
    <img src="photo5.jpg" alt="Description 5">
    <img src="photo6.jpg" alt="Description 6">
</div>

<style>
.galerie {
    display: grid;
    grid-template-columns: repeat(3, 1fr);  /* 3 colonnes égales */
    gap: 15px;
    padding: 20px;
}

.galerie img {
    width: 100%;
    height: 250px;
    object-fit: cover;     /* Recadre sans déformer */
    border-radius: 8px;
    transition: transform 0.3s;
}

.galerie img:hover {
    transform: scale(1.05);
}
</style>

Galerie responsive avec auto-fill

La grille s’adapte automatiquement au nombre de colonnes selon la largeur :

.galerie-auto {
    display: grid;
    /* Colonnes de minimum 280px, maximum 1fr */
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 15px;
    padding: 20px;
}

/* Plus besoin de media queries ! La grille s'adapte toute seule :
   - Mobile : 1 colonne
   - Tablette : 2 colonnes
   - Desktop : 3-4 colonnes
*/

Galerie style Pinterest (Masonry)

Voici la mise en pratique pour Galerie style Pinterest (Masonry). Le bloc ci-dessous est copiable directement dans votre projet, lisible ligne par ligne. Lisez-le une première fois en survol pour repérer la structure générale, puis adaptez les noms de variables, identifiants et valeurs à votre contexte avant de l’exécuter en local.

<div class="galerie-pinterest">
    <div class="item"><img src="tall.jpg" alt=""></div>
    <div class="item wide"><img src="wide.jpg" alt=""></div>
    <div class="item tall"><img src="portrait.jpg" alt=""></div>
    <div class="item"><img src="square.jpg" alt=""></div>
    <div class="item wide tall"><img src="large.jpg" alt=""></div>
    <div class="item"><img src="normal.jpg" alt=""></div>
</div>

<style>
.galerie-pinterest {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    grid-auto-rows: 200px;
    gap: 15px;
    padding: 20px;
}

.galerie-pinterest .item {
    border-radius: 12px;
    overflow: hidden;
}

.galerie-pinterest img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Éléments qui prennent plus de place */
.item.wide { grid-column: span 2; }
.item.tall { grid-row: span 2; }

/* Responsive : pas de span sur mobile */
@media (max-width: 600px) {
    .item.wide { grid-column: span 1; }
    .item.tall { grid-row: span 1; }
}
</style>

Galerie avec overlay et légende

Voici la mise en pratique pour Galerie avec overlay et légende. Le bloc ci-dessous est copiable directement dans votre projet, lisible ligne par ligne. Lisez-le une première fois en survol pour repérer la structure générale, puis adaptez les noms de variables, identifiants et valeurs à votre contexte avant de l’exécuter en local.

<div class="galerie-overlay">
    <figure class="photo">
        <img src="photo1.jpg" alt="Plage de Ngor">
        <figcaption>
            <h3>Plage de Ngor</h3>
            <p>Dakar, Sénégal</p>
        </figcaption>
    </figure>
    <!-- Répéter pour chaque photo -->
</div>

<style>
.galerie-overlay {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 20px;
    padding: 20px;
}

.photo {
    position: relative;
    margin: 0;
    overflow: hidden;
    border-radius: 12px;
    cursor: pointer;
}

.photo img {
    width: 100%;
    height: 300px;
    object-fit: cover;
    transition: transform 0.5s;
}

.photo:hover img { transform: scale(1.1); }

.photo figcaption {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    padding: 20px;
    background: linear-gradient(transparent, rgba(0,0,0,0.8));
    color: white;
    transform: translateY(100%);
    transition: transform 0.3s;
}

.photo:hover figcaption { transform: translateY(0); }

.photo figcaption h3 { margin: 0 0 5px; font-size: 18px; }
.photo figcaption p { margin: 0; opacity: 0.8; font-size: 14px; }
</style>

Filtres par catégorie (avec JavaScript)

Voici la mise en pratique pour Filtres par catégorie (avec JavaScript). Le bloc ci-dessous est copiable directement dans votre projet, lisible ligne par ligne. Lisez-le une première fois en survol pour repérer la structure générale, puis adaptez les noms de variables, identifiants et valeurs à votre contexte avant de l’exécuter en local.

<!-- Boutons de filtres -->
<div class="filtres">
    <button class="actif" data-filtre="all">Tout</button>
    <button data-filtre="paysage">Paysages</button>
    <button data-filtre="portrait">Portraits</button>
    <button data-filtre="architecture">Architecture</button>
</div>

<div class="galerie-filtrable">
    <div class="photo" data-categorie="paysage">...</div>
    <div class="photo" data-categorie="portrait">...</div>
    <div class="photo" data-categorie="architecture">...</div>
</div>

<script>
document.querySelectorAll('.filtres button').forEach(btn => {
    btn.addEventListener('click', () => {
        // Activer le bouton
        document.querySelector('.filtres .actif').classList.remove('actif');
        btn.classList.add('actif');
        
        const filtre = btn.dataset.filtre;
        
        document.querySelectorAll('.galerie-filtrable .photo').forEach(photo => {
            if (filtre === 'all' || photo.dataset.categorie === filtre) {
                photo.style.display = '';
                photo.style.animation = 'fadeIn 0.5s';
            } else {
                photo.style.display = 'none';
            }
        });
    });
});
</script>

Lightbox simple en CSS et JS

Voici la mise en pratique pour Lightbox simple en CSS et JS. Le bloc ci-dessous est copiable directement dans votre projet, lisible ligne par ligne. Lisez-le une première fois en survol pour repérer la structure générale, puis adaptez les noms de variables, identifiants et valeurs à votre contexte avant de l’exécuter en local.

<div id="lightbox">
    <span class="close">&times;</span>
    <img id="lightbox-img" src="" alt="">
</div>

<style>
#lightbox {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.9);
    z-index: 9999;
    align-items: center;
    justify-content: center;
}
#lightbox.actif { display: flex; }
#lightbox img { max-width: 90%; max-height: 90vh; border-radius: 8px; }
.close { position: absolute; top: 20px; right: 30px; color: white; font-size: 40px; cursor: pointer; }
</style>

<script>
const lightbox = document.getElementById('lightbox');
const lbImg = document.getElementById('lightbox-img');

document.querySelectorAll('.galerie img').forEach(img => {
    img.addEventListener('click', () => {
        lbImg.src = img.src;
        lightbox.classList.add('actif');
    });
});

lightbox.addEventListener('click', () => lightbox.classList.remove('actif'));
</script>

Erreurs fréquentes

Images étirées ou déformées

Cause : oubli de object-fit: cover ou height sans width.
Solution : appliquez width: 100%; height: 100%; object-fit: cover sur les img.

Galerie qui rame avec beaucoup d’images

Cause : toutes les images se chargent dès l’arrivée sur la page.
Solution : ajoutez loading="lazy" sur chaque img, et servez vos photos en WebP/AVIF avec srcset.

CLS (Cumulative Layout Shift) élevé

Cause : les images n’ont pas de dimensions, la page « saute » pendant le chargement.
Solution : précisez width et height sur chaque img, ou utilisez aspect-ratio sur le conteneur.

Lightbox inaccessible au clavier

Cause : pas de gestion Escape, focus piégé manquant.
Solution : écoutez keydown pour Escape, mettez tabindex, utilisez l’API native <dialog> qui gère ça gratuitement.

Exercice pratique

🎯 Défi : Galerie portfolio complète

  1. Créez une galerie avec 9 images en grille auto-fill responsive
  2. Ajoutez un overlay avec titre et description au survol
  3. Ajoutez 3 boutons de filtres par catégorie
  4. Implémentez une lightbox au clic
  5. Ajoutez loading="lazy" sur toutes les images

auto-fit vs auto-fill : choisir le bon comportement

Voici la mise en pratique pour auto-fit vs auto-fill : choisir le bon comportement. Le bloc ci-dessous est copiable directement dans votre projet, lisible ligne par ligne. Lisez-le une première fois en survol pour repérer la structure générale, puis adaptez les noms de variables, identifiants et valeurs à votre contexte avant de l’exécuter en local.

.galerie {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px;
}
Mot-clé Avec peu d’éléments Cas d’usage
auto-fit Étire les colonnes Galerie qui s’adapte au contenu
auto-fill Crée des colonnes vides invisibles Réserver la grille même vide

Pour un photographe à Abidjan, auto-fit donne un meilleur rendu : avec 2 photos, elles occupent toute la largeur ; avec 20, la grille se densifie automatiquement.

Masonry natif avec grid-template-rows

L’effet « mur de pierres » à la Pinterest où chaque image garde sa hauteur naturelle ne nécessite plus JavaScript en 2026.

.galerie-masonry {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  grid-template-rows: masonry;
  gap: 12px;
}
@supports not (grid-template-rows: masonry) {
  .galerie-masonry { columns: 4; column-gap: 12px; }
  .galerie-masonry > * { break-inside: avoid; margin-bottom: 12px; }
}

Spans : agrandir certaines images

Voici la mise en pratique pour Spans : agrandir certaines images. Le bloc ci-dessous est copiable directement dans votre projet, lisible ligne par ligne. Lisez-le une première fois en survol pour repérer la structure générale, puis adaptez les noms de variables, identifiants et valeurs à votre contexte avant de l’exécuter en local.

.galerie figure:nth-child(1) { grid-column: span 2; grid-row: span 2; }
.galerie figure.feature { grid-column: span 2; }

Combinez avec grid-auto-flow: dense pour que les vides soient comblés.

Formats modernes : AVIF, WebP et picture

Une image JPEG de 200 Ko devient 60 Ko en WebP et 40 Ko en AVIF. Sur une galerie de 30 photos consultée depuis une connexion 3G à Bamako, c’est 4,8 Mo économisés.

<picture>
  <source srcset='photo.avif' type='image/avif'>
  <source srcset='photo.webp' type='image/webp'>
  <img src='photo.jpg' alt='Marché de Sandaga, Dakar' loading='lazy' decoding='async' width='800' height='600'>
</picture>

Lazy loading et décodage asynchrone

L’attribut loading='lazy' est natif sur tous les navigateurs majeurs depuis 2020. Pour la première image visible (LCP), ne pas mettre lazy mais fetchpriority='high'.

Lightbox accessible sans JavaScript

Voici la mise en pratique pour Lightbox accessible sans JavaScript. Le bloc ci-dessous est copiable directement dans votre projet, lisible ligne par ligne. Lisez-le une première fois en survol pour repérer la structure générale, puis adaptez les noms de variables, identifiants et valeurs à votre contexte avant de l’exécuter en local.

<a href='#photo1'><img src='thumb1.jpg' alt='...'></a>
<div id='photo1' class='lightbox'>
  <a href='#' class='fermer'>Fermer</a>
  <img src='full1.jpg' alt='Plage de Yoff'>
</div>
.lightbox { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.9); z-index: 1000; align-items: center; justify-content: center; }
.lightbox:target { display: flex; }
.lightbox img { max-width: 90vw; max-height: 90vh; }

Accessibilité clavier et lecteurs d’écran

  1. Toujours alt descriptif sur images informatives, vide (alt='') sur décoratives.
  2. Englober chaque vignette dans un <a href> tabulable.
  3. Style :focus-visible avec contour épais (3px solid).
  4. Utiliser <figure> et <figcaption>.

Subgrid pour aligner les légendes

Subgrid, supporté depuis 2023 sur tous les navigateurs majeurs, fait hériter les enfants de la grille parente.

.carte {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 3;
}

Pour creuser ce sujet sur les patterns frontend modernes adaptés au contexte ouest-africain, voir aussi le guide événements JavaScript et le guide media queries responsive. Tester systématiquement sur Galaxy A03 ou équivalent à 200 EUR avec 4G locale dégradée reste le seul juge fiable de la performance perçue par la majorité des visiteurs locaux.

Pour étoffer le tableau sur les patterns frontend modernes adaptés au contexte ouest-africain, voir aussi le guide événements JavaScript et le guide media queries responsive. Tester systématiquement sur Galaxy A03 ou équivalent à 200 EUR avec 4G locale dégradée reste le seul juge fiable de la performance perçue par la majorité des visiteurs locaux.

Sur le même thème sur les patterns frontend modernes adaptés au contexte ouest-africain, voir aussi le guide événements JavaScript et le guide media queries responsive. Tester systématiquement sur Galaxy A03 ou équivalent à 200 EUR avec 4G locale dégradée reste le seul juge fiable de la performance perçue par la majorité des visiteurs locaux.

Dans la continuité sur les patterns frontend modernes adaptés au contexte ouest-africain, voir aussi le guide événements JavaScript et le guide media queries responsive. Tester systématiquement sur Galaxy A03 ou équivalent à 200 EUR avec 4G locale dégradée reste le seul juge fiable de la performance perçue par la majorité des visiteurs locaux.

Adaptation au contexte ouest-africain : performance, équipe, marché

Pour un développeur basé à Dakar, Abidjan, Bamako, Cotonou, Lomé, Ouagadougou, Niamey ou Conakry qui livre des sites web ou applications custom à des PME locales, trois adaptations pèsent sur le succès des projets. Premièrement, la connectivité 4G inégale impose de réduire le poids des pages au strict nécessaire — un site qui charge en 6 secondes perd 40 % de ses visiteurs avant l’affichage. Deuxièmement, le profil typique des développeurs disponibles sur le marché local est majoritairement formé sur du JavaScript moderne et du PHP, avec une expertise variable sur les outils plus avancés (TypeScript, frameworks edge, design systems). Adapter la pile technique au profil d’équipe disponible sur place évite la dette technique liée au turn-over.

Troisièmement, le coût en FCFA des services cloud doit être anticipé dans le budget. Hetzner CX22 à 4 500 FCFA/mois reste imbattable pour un démarrage, Cloudflare Pages gratuit pour les sites statiques, Backblaze B2 à 6 USD/TB/mois pour les sauvegardes. Pour les projets B2C qui exigent une latence faible, héberger sur un CDN avec PoP africain (Cloudflare Lagos, Africa Data Centres) divise par trois la latence perçue par rapport à un déploiement européen sans CDN.

Tester sur appareils réels avant la mise en production

Plus important que tous les outils synthétiques, tester son site sur un Android d’entrée de gamme avec une connexion 4G locale dégradée donne le seul verdict qui compte. Galaxy A03 à 200 EUR neuf, Tecno Spark, Itel A60 sont les appareils dominants chez les visiteurs ouest-africains. Sur ces téléphones, un site qui semble rapide sur DevTools peut être laggy en réalité. Trois conseils pratiques. Premièrement, garder un appareil de test physique à portée de main pour vérifier chaque livraison majeure. Deuxièmement, profiler avec Chrome DevTools en mode Slow 4G + 4x CPU slowdown pour simuler ces conditions. Troisièmement, mesurer en conditions réelles avec PageSpeed Insights qui agrège les données du Chrome User Experience Report sur 28 jours — la donnée terrain qui compte vraiment.

Erreurs courantes en développement web à éviter

Trois patterns reviennent dans les projets web mal exécutés et coûtent cher à corriger plus tard. Premier pattern : copier-coller de code Stack Overflow sans comprendre le contexte d’origine. Une solution qui marche pour un cas particulier devient un bug subtil dans un autre — toujours adapter au contexte de votre projet. Deuxième pattern : ignorer les warnings de la console. Chaque warning est un signal qui mérite d’être lu et compris. Une console pleine de warnings finit par cacher les vrais problèmes. Troisième pattern : ne pas tester sur de vrais appareils. DevTools simule mais ne remplace pas un test physique sur un Android d’entrée de gamme avec une 4G dégradée — c’est le contexte réel d’usage de la majorité des visiteurs locaux.

Pour creuser ce sujet sur les bonnes pratiques de développement web modernes adaptées au contexte ouest-africain, voir aussi le guide responsive design et le guide Bootstrap 5. Documenter chaque décision technique majeure dans un fichier ADR (Architecture Decision Record) prend dix minutes et fait gagner des heures lors d’un incident ou d’un audit.

Pour les équipes qui maintiennent plusieurs galeries sur un même site, centraliser le composant dans un Web Component ou une partial template économise des heures de duplication.

Dans la continuité

Service ITSkillsCenter

Application mobile Android et iOS

Création d'application mobile Android et iOS. À partir de 350 000 FCFA.

Démarrer mon projet
Publicité