Bootstrap 5
Lightbox
Galerie
Thumbnails
Dark Mode
Template
Javascript
Galerie d'images avec lightbox et miniatures en Bootstrap 5 Dark : navigation clavier, zoom, transitions fluides et affichage plein écran responsive.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="copyright" content="AngularForAll" />
<meta name="author" content="AngularForAll" />
<meta name="robots" content="noindex, nofollow" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Cache-Control" content="public, max-age=604800" />
<title>Snippets Lightbox Thumbnails Bootstrap5 2026 05022044 | AngularForAll</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
<style>
:root {
--bg-dark: #0a0a0a;
--bg-card: #1a1a1a;
--bg-surface: #242424;
--border-color: #333;
--text-primary: #ffffff;
--text-secondary: #b0b0b0;
--accent: #6c5ce7;
--accent-hover: #5b4bd5;
}
body {
background: var(--bg-dark);
color: var(--text-primary);
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
/* Gallery Container */
.gallery-container {
background: var(--bg-card);
border-radius: 20px;
padding: 2rem;
max-width: 900px;
width: 100%;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
border: 1px solid var(--border-color);
}
.gallery-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
.gallery-title {
font-size: 1.5rem;
font-weight: 700;
color: var(--text-primary);
margin: 0;
}
.gallery-badge {
background: var(--accent);
color: white;
padding: 0.4rem 1rem;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
}
/* Main Image Container */
.main-image-wrapper {
position: relative;
background: var(--bg-surface);
border-radius: 16px;
overflow: hidden;
margin-bottom: 1.5rem;
aspect-ratio: 16/10;
cursor: zoom-in;
border: 1px solid var(--border-color);
}
.main-image-wrapper img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.main-image-wrapper:hover img {
transform: scale(1.05);
}
.main-image-wrapper.zoomed img {
transform: scale(2);
cursor: zoom-out;
}
/* Navigation Arrows */
.gallery-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
background: rgba(0, 0, 0, 0.7);
color: white;
border: 2px solid rgba(255, 255, 255, 0.3);
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
z-index: 10;
backdrop-filter: blur(10px);
}
.gallery-nav:hover {
background: rgba(108, 92, 231, 0.8);
border-color: var(--accent);
transform: translateY(-50%) scale(1.1);
}
.gallery-nav.prev {
left: 1rem;
}
.gallery-nav.next {
right: 1rem;
}
.gallery-nav i {
font-size: 1.5rem;
}
/* Image Counter */
.image-counter {
position: absolute;
bottom: 1rem;
right: 1rem;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 0.4rem 0.8rem;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 500;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
/* Zoom Indicator */
.zoom-indicator {
position: absolute;
top: 1rem;
right: 1rem;
background: rgba(0, 0, 0, 0.7);
color: white;
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.main-image-wrapper:hover .zoom-indicator {
opacity: 1;
}
/* Thumbnails */
.thumbnails-container {
position: relative;
}
.thumbnails-wrapper {
overflow: hidden;
border-radius: 12px;
}
.thumbnails-track {
display: flex;
gap: 0.75rem;
transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.thumbnail-item {
flex: 0 0 calc(20% - 0.6rem);
aspect-ratio: 16/10;
border-radius: 10px;
overflow: hidden;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.3s ease;
position: relative;
}
.thumbnail-item:hover {
border-color: rgba(108, 92, 231, 0.5);
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(108, 92, 231, 0.3);
}
.thumbnail-item.active {
border-color: var(--accent);
box-shadow: 0 0 20px rgba(108, 92, 231, 0.4);
}
.thumbnail-item img {
width: 100%;
height: 100%;
object-fit: cover;
}
.thumbnail-item .active-overlay {
position: absolute;
inset: 0;
background: rgba(108, 92, 231, 0.2);
opacity: 0;
transition: opacity 0.3s ease;
}
.thumbnail-item.active .active-overlay {
opacity: 1;
}
/* Thumbnail Navigation */
.thumb-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
background: var(--bg-surface);
color: white;
border: 1px solid var(--border-color);
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
z-index: 5;
}
.thumb-nav:hover {
background: var(--accent);
border-color: var(--accent);
}
.thumb-nav.prev-thumb {
left: -18px;
}
.thumb-nav.next-thumb {
right: -18px;
}
.thumb-nav:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.thumb-nav:disabled:hover {
background: var(--bg-surface);
border-color: var(--border-color);
}
/* Lightbox */
.lightbox-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.95);
z-index: 9999;
display: none;
align-items: center;
justify-content: center;
backdrop-filter: blur(20px);
}
.lightbox-overlay.active {
display: flex;
}
.lightbox-content {
position: relative;
max-width: 90vw;
max-height: 90vh;
}
.lightbox-content img {
max-width: 90vw;
max-height: 85vh;
object-fit: contain;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.lightbox-close {
position: absolute;
top: -50px;
right: 0;
background: rgba(255, 255, 255, 0.1);
color: white;
border: 1px solid rgba(255, 255, 255, 0.3);
width: 44px;
height: 44px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
font-size: 1.5rem;
}
.lightbox-close:hover {
background: rgba(255, 0, 0, 0.5);
transform: rotate(90deg);
}
.lightbox-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
background: rgba(255, 255, 255, 0.1);
color: white;
border: 1px solid rgba(255, 255, 255, 0.3);
width: 56px;
height: 56px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
font-size: 1.5rem;
}
.lightbox-nav:hover {
background: var(--accent);
border-color: var(--accent);
}
.lightbox-nav.prev {
left: -70px;
}
.lightbox-nav.next {
right: -70px;
}
.lightbox-counter {
position: absolute;
bottom: -40px;
left: 50%;
transform: translateX(-50%);
color: white;
font-size: 0.95rem;
font-weight: 500;
}
.lightbox-thumbnails {
display: flex;
gap: 0.5rem;
justify-content: center;
margin-top: 1rem;
}
.lightbox-thumb {
width: 60px;
height: 40px;
border-radius: 6px;
overflow: hidden;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.3s ease;
opacity: 0.5;
}
.lightbox-thumb:hover {
opacity: 0.8;
}
.lightbox-thumb.active {
border-color: var(--accent);
opacity: 1;
}
.lightbox-thumb img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* Animations */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.lightbox-overlay.active {
animation: fadeIn 0.3s ease;
}
/* Responsive */
@media (max-width: 768px) {
.gallery-container {
padding: 1.25rem;
border-radius: 16px;
}
.thumbnail-item {
flex: 0 0 calc(25% - 0.56rem);
}
.gallery-nav {
width: 40px;
height: 40px;
}
.gallery-nav i {
font-size: 1.2rem;
}
.lightbox-nav {
width: 44px;
height: 44px;
}
.lightbox-nav.prev {
left: 10px;
}
.lightbox-nav.next {
right: 10px;
}
}
@media (max-width: 480px) {
.thumbnail-item {
flex: 0 0 calc(33.33% - 0.5rem);
}
}
</style>
</head>
<body>
<div class="gallery-container">
<!-- Header -->
<div class="gallery-header">
<h2 class="gallery-title">
<i class="bi bi-images me-2"></i>Galerie Produit
</h2>
<span class="gallery-badge">8 Photos</span>
</div>
<!-- Main Image -->
<div class="main-image-wrapper" id="mainImageWrapper">
<img id="mainImage" src="" alt="Image principale">
<button class="gallery-nav prev" onclick="navigateImage(-1)" title="Précédent">
<i class="bi bi-chevron-left"></i>
</button>
<button class="gallery-nav next" onclick="navigateImage(1)" title="Suivant">
<i class="bi bi-chevron-right"></i>
</button>
<div class="zoom-indicator" id="zoomIndicator">
<i class="bi bi-zoom-in"></i>
</div>
<div class="image-counter" id="imageCounter">1 / 8</div>
</div>
<!-- Thumbnails -->
<div class="thumbnails-container">
<button class="thumb-nav prev-thumb" onclick="scrollThumbnails(-1)" id="prevThumbBtn" disabled>
<i class="bi bi-chevron-left"></i>
</button>
<div class="thumbnails-wrapper" id="thumbnailsWrapper">
<div class="thumbnails-track" id="thumbnailsTrack"></div>
</div>
<button class="thumb-nav next-thumb" onclick="scrollThumbnails(1)" id="nextThumbBtn">
<i class="bi bi-chevron-right"></i>
</button>
</div>
</div>
<!-- Lightbox -->
<div class="lightbox-overlay" id="lightboxOverlay">
<div class="lightbox-content">
<button class="lightbox-close" onclick="closeLightbox()">
<i class="bi bi-x-lg"></i>
</button>
<button class="lightbox-nav prev" onclick="navigateLightbox(-1)">
<i class="bi bi-chevron-left"></i>
</button>
<img id="lightboxImage" src="" alt="Lightbox image">
<button class="lightbox-nav next" onclick="navigateLightbox(1)">
<i class="bi bi-chevron-right"></i>
</button>
<div class="lightbox-counter" id="lightboxCounter">1 / 8</div>
<div class="lightbox-thumbnails" id="lightboxThumbnails"></div>
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Données des images (URLs de placeholder)
const images = [
{ src: 'https://picsum.photos/800/500?random=1', alt: 'Produit vue 1' },
{ src: 'https://picsum.photos/800/500?random=2', alt: 'Produit vue 2' },
{ src: 'https://picsum.photos/800/500?random=3', alt: 'Produit vue 3' },
{ src: 'https://picsum.photos/800/500?random=4', alt: 'Produit vue 4' },
{ src: 'https://picsum.photos/800/500?random=5', alt: 'Produit vue 5' },
{ src: 'https://picsum.photos/800/500?random=6', alt: 'Produit vue 6' },
{ src: 'https://picsum.photos/800/500?random=7', alt: 'Produit vue 7' },
{ src: 'https://picsum.photos/800/500?random=8', alt: 'Produit vue 8' },
];
let currentIndex = 0;
let isZoomed = false;
let thumbScrollPosition = 0;
// Initialisation
function initGallery() {
renderThumbnails();
updateMainImage(0);
setupZoom();
setupKeyboard();
}
// Rendu des thumbnails
function renderThumbnails() {
const track = document.getElementById('thumbnailsTrack');
track.innerHTML = images.map((img, index) => `
<div class="thumbnail-item ${index === currentIndex ? 'active' : ''}"
onclick="updateMainImage(${index})"
data-index="${index}">
<img src="${img.src}" alt="${img.alt}" loading="lazy">
<div class="active-overlay"></div>
</div>
`).join('');
renderLightboxThumbnails();
}
// Mise à jour image principale
function updateMainImage(index) {
currentIndex = index;
isZoomed = false;
const mainImage = document.getElementById('mainImage');
const wrapper = document.getElementById('mainImageWrapper');
mainImage.src = images[index].src;
mainImage.alt = images[index].alt;
document.getElementById('imageCounter').textContent = `${index + 1} / ${images.length}`;
// Animation
mainImage.style.opacity = '0';
mainImage.style.transform = 'scale(1)';
wrapper.classList.remove('zoomed');
setTimeout(() => {
mainImage.style.opacity = '1';
}, 150);
// Mise à jour thumbnails actifs
document.querySelectorAll('.thumbnail-item').forEach((thumb, i) => {
thumb.classList.toggle('active', i === index);
});
document.querySelectorAll('.lightbox-thumb').forEach((thumb, i) => {
thumb.classList.toggle('active', i === index);
});
// Scroll to thumbnail
scrollToThumbnail(index);
}
// Navigation
function navigateImage(direction) {
let newIndex = currentIndex + direction;
if (newIndex < 0) newIndex = images.length - 1;
if (newIndex >= images.length) newIndex = 0;
updateMainImage(newIndex);
}
// Zoom toggle
function setupZoom() {
const wrapper = document.getElementById('mainImageWrapper');
wrapper.addEventListener('click', (e) => {
if (e.target.closest('.gallery-nav')) return;
isZoomed = !isZoomed;
wrapper.classList.toggle('zoomed', isZoomed);
const indicator = document.getElementById('zoomIndicator');
indicator.innerHTML = isZoomed
? '<i class="bi bi-zoom-out"></i>'
: '<i class="bi bi-zoom-in"></i>';
});
}
// Scroll thumbnails
function scrollThumbnails(direction) {
const wrapper = document.getElementById('thumbnailsWrapper');
const scrollAmount = wrapper.offsetWidth * 0.8;
thumbScrollPosition += direction * scrollAmount;
const maxScroll = document.getElementById('thumbnailsTrack').scrollWidth - wrapper.offsetWidth;
thumbScrollPosition = Math.max(0, Math.min(thumbScrollPosition, maxScroll));
document.getElementById('thumbnailsTrack').style.transform = `translateX(-${thumbScrollPosition}px)`;
updateThumbNavButtons();
}
function scrollToThumbnail(index) {
const wrapper = document.getElementById('thumbnailsWrapper');
const thumbWidth = wrapper.offsetWidth / 5;
thumbScrollPosition = index * thumbWidth - thumbWidth;
const maxScroll = document.getElementById('thumbnailsTrack').scrollWidth - wrapper.offsetWidth;
thumbScrollPosition = Math.max(0, Math.min(thumbScrollPosition, maxScroll));
document.getElementById('thumbnailsTrack').style.transform = `translateX(-${thumbScrollPosition}px)`;
updateThumbNavButtons();
}
function updateThumbNavButtons() {
const maxScroll = document.getElementById('thumbnailsTrack').scrollWidth - document.getElementById('thumbnailsWrapper').offsetWidth;
document.getElementById('prevThumbBtn').disabled = thumbScrollPosition <= 0;
document.getElementById('nextThumbBtn').disabled = thumbScrollPosition >= maxScroll - 1;
}
// Lightbox
function openLightbox() {
const overlay = document.getElementById('lightboxOverlay');
overlay.classList.add('active');
document.body.style.overflow = 'hidden';
updateLightboxImage();
}
function closeLightbox() {
const overlay = document.getElementById('lightboxOverlay');
overlay.classList.remove('active');
document.body.style.overflow = '';
}
function updateLightboxImage() {
document.getElementById('lightboxImage').src = images[currentIndex].src;
document.getElementById('lightboxCounter').textContent = `${currentIndex + 1} / ${images.length}`;
document.querySelectorAll('.lightbox-thumb').forEach((thumb, i) => {
thumb.classList.toggle('active', i === currentIndex);
});
}
function navigateLightbox(direction) {
let newIndex = currentIndex + direction;
if (newIndex < 0) newIndex = images.length - 1;
if (newIndex >= images.length) newIndex = 0;
updateMainImage(newIndex);
updateLightboxImage();
}
function renderLightboxThumbnails() {
const container = document.getElementById('lightboxThumbnails');
container.innerHTML = images.map((img, index) => `
<div class="lightbox-thumb ${index === currentIndex ? 'active' : ''}"
onclick="updateMainImage(${index}); updateLightboxImage();">
<img src="${img.src}" alt="${img.alt}" loading="lazy">
</div>
`).join('');
}
// Double-clic pour lightbox
function setupKeyboard() {
document.addEventListener('keydown', (e) => {
const overlay = document.getElementById('lightboxOverlay');
if (overlay.classList.contains('active')) {
if (e.key === 'Escape') closeLightbox();
if (e.key === 'ArrowLeft') navigateLightbox(-1);
if (e.key === 'ArrowRight') navigateLightbox(1);
}
});
}
// Double-clic sur image principale pour ouvrir lightbox
document.getElementById('mainImageWrapper').addEventListener('dblclick', (e) => {
if (!e.target.closest('.gallery-nav')) {
openLightbox();
}
});
// Fermer lightbox en cliquant sur l'overlay
document.getElementById('lightboxOverlay').addEventListener('click', (e) => {
if (e.target === document.getElementById('lightboxOverlay')) {
closeLightbox();
}
});
// Initialisation
document.addEventListener('DOMContentLoaded', () => {
initGallery();
updateThumbNavButtons();
});
</script>
</body>
</html>
Télécharger le fichier source