GalerieGratuit

Basic Lightbox

Visionneuse minimale : une vignette cliquée s'ouvre en grand dans une vraie boîte de dialogue, sur fond noir flouté ; ✕, clic à côté ou Échap la referment.

JSModalFade

Mis à jour le

Survolez ou cliquez la scène pour interagir

Cet effet est gratuit — la catégorie Galerie en contient 44 au total, dont 6 gratuits. Effect.Labs regroupe 840 effets vanilla. Découvrir la catégorie →

Exemples d'usage

Halle Arvendel

Venir à la salle

12 quai des Tanneurs · ouverture des portes à 19 h 30

  • MLigne 2, arrêt Halle — 3 min à pied
  • BBus 14 et 31, arrêt Pont-Neuf
  • PParking du Quai, 420 places, ouvert jusqu'à 1 h

Plan d'accès — touchez pour agrandir

① Plan d'accès d'une salle de concert, rangé à côté des lignes de transport

QuandSur la page « Infos pratiques » de la Halle Arvendel, le plan tient dans une petite carte à droite des arrêts de métro et de bus. Un clic l'ouvre en grand pour repérer la sortie et le parking.
PourquoiLa page reste lisible avec un plan réduit à une vignette, et le détail des rues n'est montré qu'à qui le demande. Une seule vignette suffit : la visionneuse n'a pas besoin de flèches.
RéglagesRacine posée dans une carte blanche (width: min(40%, 290px)), .blb-thumb à width: 100%, --blb-accent passé au rouge de la salle. Le texte « touchez pour agrandir » est un simple paragraphe dans la racine.
Collège Sorbelane — espace familleLina Morvaille, 4e B

Bulletins 2025-2026

1er trimestreMoyenne13,8PDF
2e trimestreMoyenne14,6PDF
3e trimestreMoyenne15,1PDF

② Bulletins des trois trimestres dans l'espace famille d'un collège

QuandLina Morvaille a trois cartes de bulletin ; le parent clique sur l'aperçu du 2e trimestre pour lire la page entière sans télécharger le PDF.
PourquoiChaque carte garde sa moyenne et son lien PDF à côté de l'aperçu : le bouton-vignette fait partie de la carte, la visionneuse sert seulement à lire le document en grand.
RéglagesLes trois cartes sont dans .blb-grid, passée en grille de trois colonnes ; les vignettes prennent toute la largeur de la carte et cadrent le haut du document (object-position: top sur l'image).

Calvanelle

Carte du midi, sur le téléphone à table : touchez une photo pour voir le plat en grand.

Formule midi 24 €

Calvanelle
EntréesPlatsDesserts
Risotto aux cèpeslait · céleri
18 €
Bar rôti, légumespoisson
22 €
Tarte abricotgluten · œuf
8 €

③ Photo d'un plat dans la carte d'un restaurant consultée sur téléphone

QuandÀ table, le client ouvre la carte du jour de Calvanelle sur son téléphone ; il touche la petite photo du bar rôti pour la voir en plein écran avant de commander.
PourquoiSur un écran étroit, des photos de 58 px laissent la place aux prix et aux allergènes ; le toucher ouvre l'image en grand et un toucher à côté la referme.
RéglagesUne ligne par plat dans .blb-grid (passée en colonne), vignette de 58 px avec --blb-radius: 8px. Sur la page de l'effet, la scène bascule en version téléphone seule sous 470 px de large.

Comment ça marche

Chaque vignette est un vrai <button class="blb-thumb"> qui contient votre <img> (avec son alt). Au clic, au toucher, ou avec Entrée / Espace au clavier, le script remplit l'image de la visionneuse avec l'attribut data-full du bouton s'il existe, sinon avec l'image de la vignette elle-même, puis ouvre le <dialog class="blb-viewer"> avec showModal().

Le fond (rgba(0, 0, 0, 0.9) et un flou de 10 px) apparaît en fondu pendant que l'image passe de 80 % à 100 % de sa taille, en 0,4 s avec un léger rebond. L'image s'affiche à sa taille naturelle, plafonnée à 80 % de la largeur et 70 % de la hauteur de la fenêtre.

Comme c'est un <dialog> modal, la visionneuse passe au-dessus de tout, même si la galerie est dans un conteneur transformé ou masqué. Tout le code est rangé sous .fx-0171-root et le préfixe blb- ; chaque racine est câblée séparément, donc plusieurs galeries cohabitent sur une page et coller le script deux fois ne double rien.

Accessibilité

  • Clavier : les vignettes sont des boutons, atteignables au Tab et activables avec Entrée ou Espace. À l'ouverture, le focus va sur le bouton ✕ ; Échap ferme.
  • Boîte de dialogue : le <dialog> ouvert par showModal() a le rôle dialog et le comportement modal ; il porte un nom (aria-label="Visionneuse d'images"). Le reste de la page devient inerte : le Tab passe des boutons de la visionneuse à la barre du navigateur, jamais à la page cachée derrière.
  • Retour du focus : à la fermeture, le focus revient sur la vignette qui avait ouvert l'image (mesuré dans Chromium, Firefox et WebKit).
  • Texte alternatif : l'alt de votre vignette est recopié sur la grande image. Écrivez-le pour décrire la photo, pas « image 1 ».
  • Mouvement réduit : sous prefers-reduced-motion: reduce, ni fondu ni agrandissement : l'image s'affiche et se referme d'un coup.

Compatibilité navigateur

Repose sur <dialog> et showModal() (Chrome 37, Firefox 98, Safari 15.4) et sur aspect-ratio pour les vignettes (Chrome 88, Firefox 89, Safari 15), versions lues dans @mdn/browser-compat-data. Testé dans Chromium 151, Firefox 153 et WebKit 26.5 : deux galeries sur la même page, clavier, toucher à 390 px, mouvement réduit.

Chrome 88+✓ Complet (mesuré)
Firefox 98+✓ Complet (mesuré)
Safari 15.4+✓ Complet (mesuré sur WebKit)
Edge 88+✓ Complet (moteur Chromium)
Mobile iOS 15.4+✓ Complet (non mesuré sur appareil)
Android Chrome 88+✓ Complet (toucher mesuré)

Sans showModal() (navigateur plus ancien que ces versions), le script ne fait rien et les vignettes restent de simples images. Sans backdrop-filter (Firefox avant 103), le fond est noir à 90 % sans flou.

Le code

Copiez les trois blocs dans votre page. Aucune dépendance.

HTML
<div class="fx-0171-root">
  <div class="blb-grid">
    <button type="button" class="blb-thumb" aria-haspopup="dialog"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='400' viewBox='0 0 600 400'%3E%3ClinearGradient id='g' x2='1' y2='1'%3E%3Cstop stop-color='%236366f1'/%3E%3Cstop offset='1' stop-color='%238b5cf6'/%3E%3C/linearGradient%3E%3Crect width='600' height='400' fill='url(%23g)'/%3E%3C/svg%3E" alt="Dégradé indigo vers violet" width="600" height="400"></button>
    <button type="button" class="blb-thumb" aria-haspopup="dialog"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='400' viewBox='0 0 600 400'%3E%3ClinearGradient id='g' x2='1' y2='1'%3E%3Cstop stop-color='%23d946ef'/%3E%3Cstop offset='1' stop-color='%23f43f5e'/%3E%3C/linearGradient%3E%3Crect width='600' height='400' fill='url(%23g)'/%3E%3C/svg%3E" alt="Dégradé fuchsia vers rose" width="600" height="400"></button>
    <button type="button" class="blb-thumb" aria-haspopup="dialog"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='400' viewBox='0 0 600 400'%3E%3ClinearGradient id='g' x2='1' y2='1'%3E%3Cstop stop-color='%2306b6d4'/%3E%3Cstop offset='1' stop-color='%2310b981'/%3E%3C/linearGradient%3E%3Crect width='600' height='400' fill='url(%23g)'/%3E%3C/svg%3E" alt="Dégradé cyan vers émeraude" width="600" height="400"></button>
    <button type="button" class="blb-thumb" aria-haspopup="dialog"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='400' viewBox='0 0 600 400'%3E%3ClinearGradient id='g' x2='1' y2='1'%3E%3Cstop stop-color='%23f59e0b'/%3E%3Cstop offset='1' stop-color='%23ef4444'/%3E%3C/linearGradient%3E%3Crect width='600' height='400' fill='url(%23g)'/%3E%3C/svg%3E" alt="Dégradé ambre vers rouge" width="600" height="400"></button>
  </div>
  <dialog class="blb-viewer" aria-label="Visionneuse d'images">
    <div class="blb-content">
      <button type="button" class="blb-close" aria-label="Fermer"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M18 6L6 18M6 6l12 12"/></svg></button>
      <img class="blb-image" alt="">
    </div>
  </dialog>
</div>
CSS
/* fx-0171 — scoped to .fx-0171-root and the blb- prefix: two copies on one page stay independent */
.fx-0171-root,
.fx-0171-root *,
.fx-0171-root *::before,
.fx-0171-root *::after {
  box-sizing: border-box;
}

.fx-0171-root {
  --blb-thumb: 120px;                 /* thumbnail width */
  --blb-gap: 10px;                    /* space between thumbnails */
  --blb-radius: 0.5rem;               /* thumbnail corners */
  --blb-radius-lg: 1rem;              /* large image corners */
  --blb-backdrop: rgba(0, 0, 0, 0.9); /* viewer background */
  --blb-accent: rgba(99, 102, 241, 0.3);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 300px;
  padding: 16px;
  position: relative;
  background: #0a0a0f;
  color: #fff;
  font-family: sans-serif;
}

/* Thumbnails: real buttons wrapping real images */
.blb-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--blb-gap);
  width: 100%;
}
.blb-thumb {
  display: block;
  width: var(--blb-thumb);
  max-width: 100%;
  aspect-ratio: 3 / 2;
  padding: 0;
  border: 0;
  border-radius: var(--blb-radius);
  overflow: hidden;
  background: #1a1a24;
  cursor: pointer;
  transition: transform 0.3s, box-shadow 0.3s;
}
.blb-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.blb-thumb:hover,
.blb-thumb:focus-visible {
  transform: scale(1.05);
  box-shadow: var(--blb-accent) 0 10px 30px;
}
.blb-thumb:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 3px;
}

/* Viewer: native modal <dialog> (top layer, focus kept inside, Escape) */
.blb-viewer {
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  overflow: hidden;
  background: var(--blb-backdrop);
  color: #fff;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  opacity: 0;
  transition: opacity 0.4s ease;
}
.blb-viewer[open] {
  display: flex;
  align-items: center;
  justify-content: center;
}
.blb-viewer.is-open {
  opacity: 1;
}
.blb-viewer::backdrop {
  background: transparent;
}
.blb-viewer button:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 3px;
}

.blb-content {
  position: relative;
  max-width: 80vw;
  transform: scale(0.8);
  transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.blb-viewer.is-open .blb-content {
  transform: scale(1);
}

.blb-image {
  display: block;
  width: auto;
  height: auto;
  max-width: 80vw;
  max-height: 70vh;
  border-radius: var(--blb-radius-lg);
  touch-action: pan-y pinch-zoom;
  -webkit-user-select: none;
  user-select: none;
}

/* Close button */
.blb-close {
  position: absolute;
  top: -44px;
  right: 0;
  width: 36px;
  height: 36px;
  background: rgba(255, 255, 255, 0.1);
  border: none;
  border-radius: 50%;
  color: white;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.3s ease, transform 0.3s ease;
}
.blb-close:hover {
  background: rgba(255, 255, 255, 0.2);
  transform: rotate(90deg);
}

/* Reduced motion: the viewer opens and closes at once, nothing scales */
@media (prefers-reduced-motion: reduce) {
  .blb-thumb,
  .blb-viewer,
  .blb-content,
  .blb-close,
  .blb-nav {
    transition: none;
  }
  .blb-content,
  .blb-thumb:hover,
  .blb-thumb:focus-visible {
    transform: none;
  }
}
JavaScript (fx-0171)
(function () {
  'use strict';
  // Basic Lightbox — one independent instance per .fx-0171-root (pasting the script twice is harmless)
  var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
  var CLOSE_MS = 400; // matches the CSS closing transition

  function setup(root) {
    if (root.hasAttribute('data-blb-ready')) return;
    var thumbs = Array.prototype.slice.call(root.querySelectorAll('.blb-thumb'));
    var viewer = root.querySelector('.blb-viewer');
    if (!thumbs.length || !viewer || typeof viewer.showModal !== 'function') return;
    root.setAttribute('data-blb-ready', '');
    var image = viewer.querySelector('.blb-image');
    var index = 0, opener = null, closeTimer = 0, downOnBackdrop = false;
    image.draggable = false;

    // Large version: data-full on the button, otherwise the thumbnail image itself
    function sourceOf(thumb) {
      var small = thumb.querySelector('img');
      return {
        src: thumb.getAttribute('data-full') || (small ? small.currentSrc || small.src : ''),
        alt: small ? small.alt : ''
      };
    }

    function show(i) {
      index = i;
      var s = sourceOf(thumbs[i]);
      image.src = s.src;
      image.alt = s.alt;
    }

    function open(i) {
      clearTimeout(closeTimer);
      opener = thumbs[i];
      if (!viewer.open) viewer.showModal();
      show(i);
      void viewer.offsetWidth; // start the opening transition from the closed state
      viewer.classList.add('is-open');
    }
    function close() {
      if (!viewer.open) return;
      viewer.classList.remove('is-open');
      clearTimeout(closeTimer);
      if (reduceMotion.matches) viewer.close();
      else closeTimer = setTimeout(function () { viewer.close(); }, CLOSE_MS);
    }

    thumbs.forEach(function (thumb, i) {
      thumb.addEventListener('click', function () { open(i); });
    });
    viewer.querySelector('.blb-close').addEventListener('click', close);

    // Click on the dark background (not on the image or a button) closes
    viewer.addEventListener('pointerdown', function (e) { downOnBackdrop = e.target === viewer; });
    viewer.addEventListener('click', function (e) {
      if (e.target === viewer && downOnBackdrop) close();
    });
    viewer.addEventListener('cancel', function (e) { e.preventDefault(); close(); });

    // Keyboard: Escape closes
    viewer.addEventListener('keydown', function (e) {
      if (e.key === 'Escape') { e.preventDefault(); close(); }
    });

    // Closed: focus goes back to the thumbnail that opened the viewer
    viewer.addEventListener('close', function () {
      clearTimeout(closeTimer);
      viewer.classList.remove('is-open');
      if (opener) opener.focus();
    });
  }

  function start() {
    document.querySelectorAll('.fx-0171-root').forEach(setup);
  }
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
  else start();
})();

Personnaliser

Options passées à l'API ou attributs data-* :

Option / propriétéDéfautEffet
Taille des vignettes (CSS — --blb-thumb) 120px Largeur de chaque vignette (format 3:2). La rangée se replie toute seule sur plusieurs lignes.
Espace entre vignettes (CSS — --blb-gap) 10px Écart horizontal et vertical de la grille.
Fond de la visionneuse (CSS — --blb-backdrop) rgba(0, 0, 0, 0.9) Couleur et opacité du voile. Le bouton ✕ est blanc : gardez un fond sombre.
Arrondis (CSS — --blb-radius / --blb-radius-lg) 0.5rem / 1rem Coins des vignettes, puis de la grande image.
Ombre au survol (CSS — --blb-accent) rgba(99, 102, 241, 0.3) Couleur de l'ombre quand la vignette est survolée ou a le focus clavier.
Grande version (HTML — data-full sur le bouton) absent (l'image de la vignette est réutilisée) Adresse d'une image plus grande, chargée seulement à l'ouverture. Utile quand la vignette est une miniature légère.
Nom de la visionneuse (HTML — aria-label du <dialog>) « Visionneuse d'images » Ce que les lecteurs d'écran annoncent à l'ouverture ; à adapter au contenu (« Plan d'accès »).

FAQ

Comment mettre mes propres photos ?

Remplacez le src et l'alt des <img> dans les boutons .blb-thumb, ajoutez ou retirez des boutons : le script les trouve tous. Pour une miniature légère et une grande image séparée, mettez l'adresse de la grande dans data-full.

La visionneuse s'affiche derrière mon en-tête collant, que faire ?

Ça ne devrait pas arriver : un <dialog> ouvert avec showModal() est placé dans la couche supérieure du navigateur, au-dessus de tous les z-index. Si vous voyez ce problème, vérifiez que le script a bien tourné (sans lui, rien ne s'ouvre).

Peut-on passer d'une image à la suivante ?

Pas avec cette version : elle montre une image à la fois et se referme. Pour des flèches, un compteur et le balayage au doigt, prenez la visionneuse avec navigation de la même catégorie.