Tailwind Css
Lightbox
Galerie
Thumbnails
Modal
Template
Javascript
Galerie d'images avec lightbox et miniatures en Tailwind CSS : thème bleu moderne, navigation fluide, modal plein écran et design utility-first.
<!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 Tailwindcss 2026 05022043 | AngularForAll</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Configuration -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
800: '#1e40af',
900: '#1e3a8a',
}
},
animation: {
'fade-in': 'fadeIn 0.3s ease-out',
'slide-up': 'slideUp 0.4s ease-out',
'zoom-in': 'zoomIn 0.3s ease-out',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
slideUp: {
'0%': { transform: 'translateY(20px)', opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' },
},
zoomIn: {
'0%': { transform: 'scale(0.9)', opacity: '0' },
'100%': { transform: 'scale(1)', opacity: '1' },
},
}
}
}
}
</script>
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5.1/css/all.min.css">
</head>
<body class="bg-gradient-to-br from-blue-50 via-white to-blue-100 min-h-screen flex items-center justify-center p-4 sm:p-8">
<!-- Gallery Container -->
<div class="w-full max-w-4xl bg-white rounded-2xl shadow-2xl p-6 sm:p-8 border border-blue-100">
<!-- Header -->
<div class="flex items-center justify-between mb-6">
<h2 class="text-2xl font-bold text-blue-900 flex items-center gap-2">
<i class="fas fa-images text-blue-500"></i>
Galerie Produit
</h2>
<span class="bg-blue-500 text-white px-4 py-1.5 rounded-full text-sm font-semibold shadow-lg shadow-blue-200">
8 Photos
</span>
</div>
<!-- Main Image Container -->
<div id="mainImageContainer" class="relative bg-gray-100 rounded-xl overflow-hidden mb-6 aspect-[16/10] cursor-zoom-in group">
<img id="mainImage" src="" alt="Image principale"
class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105">
<!-- Navigation Arrows -->
<button onclick="navigateImage(-1)"
class="absolute left-4 top-1/2 -translate-y-1/2 w-12 h-12 bg-white/90 hover:bg-blue-500 hover:text-white text-blue-700 rounded-full flex items-center justify-center shadow-lg transition-all duration-300 hover:scale-110 backdrop-blur-sm z-10">
<i class="fas fa-chevron-left text-lg"></i>
</button>
<button onclick="navigateImage(1)"
class="absolute right-4 top-1/2 -translate-y-1/2 w-12 h-12 bg-white/90 hover:bg-blue-500 hover:text-white text-blue-700 rounded-full flex items-center justify-center shadow-lg transition-all duration-300 hover:scale-110 backdrop-blur-sm z-10">
<i class="fas fa-chevron-right text-lg"></i>
</button>
<!-- Zoom Indicator -->
<div id="zoomIndicator" class="absolute top-4 right-4 w-10 h-10 bg-white/90 text-blue-600 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300 shadow-lg">
<i class="fas fa-search-plus"></i>
</div>
<!-- Image Counter -->
<div id="imageCounter" class="absolute bottom-4 right-4 bg-white/90 text-blue-800 px-3 py-1.5 rounded-full text-sm font-semibold backdrop-blur-sm shadow-lg">
1 / 8
</div>
</div>
<!-- Thumbnails -->
<div class="relative">
<button id="prevThumbBtn" onclick="scrollThumbnails(-1)" disabled
class="absolute -left-3 top-1/2 -translate-y-1/2 w-9 h-9 bg-white hover:bg-blue-500 hover:text-white text-blue-600 rounded-full flex items-center justify-center shadow-lg transition-all duration-300 z-10 border border-blue-200 disabled:opacity-30 disabled:cursor-not-allowed">
<i class="fas fa-chevron-left text-sm"></i>
</button>
<div class="overflow-hidden rounded-xl mx-4" id="thumbnailsWrapper">
<div id="thumbnailsTrack" class="flex gap-3 transition-transform duration-500 ease-out"></div>
</div>
<button id="nextThumbBtn" onclick="scrollThumbnails(1)"
class="absolute -right-3 top-1/2 -translate-y-1/2 w-9 h-9 bg-white hover:bg-blue-500 hover:text-white text-blue-600 rounded-full flex items-center justify-center shadow-lg transition-all duration-300 z-10 border border-blue-200 disabled:opacity-30 disabled:cursor-not-allowed">
<i class="fas fa-chevron-right text-sm"></i>
</button>
</div>
</div>
<!-- Lightbox Modal -->
<div id="lightboxModal" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/95 backdrop-blur-sm">
<div class="relative max-w-[90vw] max-h-[90vh]">
<!-- Close Button -->
<button onclick="closeLightbox()"
class="absolute -top-12 right-0 w-11 h-11 bg-white/10 hover:bg-red-500 text-white rounded-full flex items-center justify-center transition-all duration-300 hover:rotate-90 border border-white/20">
<i class="fas fa-times text-xl"></i>
</button>
<!-- Lightbox Navigation -->
<button onclick="navigateLightbox(-1)"
class="absolute left-4 sm:-left-16 top-1/2 -translate-y-1/2 w-12 h-12 bg-white/10 hover:bg-blue-500 text-white rounded-full flex items-center justify-center transition-all duration-300 border border-white/20 text-xl">
<i class="fas fa-chevron-left"></i>
</button>
<img id="lightboxImage" src="" alt="Lightbox"
class="max-w-[90vw] max-h-[85vh] object-contain rounded-lg shadow-2xl animate-fade-in">
<button onclick="navigateLightbox(1)"
class="absolute right-4 sm:-right-16 top-1/2 -translate-y-1/2 w-12 h-12 bg-white/10 hover:bg-blue-500 text-white rounded-full flex items-center justify-center transition-all duration-300 border border-white/20 text-xl">
<i class="fas fa-chevron-right"></i>
</button>
<!-- Counter -->
<div id="lightboxCounter" class="absolute -bottom-10 left-1/2 -translate-x-1/2 text-white font-semibold text-sm">
1 / 8
</div>
</div>
</div>
<script>
// Images
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 thumbScrollPos = 0;
function init() {
renderThumbnails();
updateMainImage(0);
setupZoom();
setupKeyboard();
}
function renderThumbnails() {
const track = document.getElementById('thumbnailsTrack');
track.innerHTML = images.map((img, i) => `
<div class="thumbnail-item flex-shrink-0 w-[calc(20%-0.6rem)] sm:w-[calc(16.66%-0.75rem)] aspect-[16/10] rounded-lg overflow-hidden cursor-pointer border-2 transition-all duration-300 hover:-translate-y-1 hover:shadow-lg relative ${i === currentIndex ? 'border-blue-500 shadow-lg shadow-blue-200' : 'border-transparent hover:border-blue-300'}"
onclick="updateMainImage(${i})">
<img src="${img.src}" alt="${img.alt}" class="w-full h-full object-cover" loading="lazy">
${i === currentIndex ? '<div class="absolute inset-0 bg-blue-500/20"></div>' : ''}
</div>
`).join('');
}
function updateMainImage(index) {
currentIndex = index;
isZoomed = false;
const mainImg = document.getElementById('mainImage');
const container = document.getElementById('mainImageContainer');
mainImg.style.opacity = '0';
mainImg.src = images[index].src;
mainImg.alt = images[index].alt;
container.classList.remove('zoomed');
setTimeout(() => mainImg.style.opacity = '1', 150);
document.getElementById('imageCounter').textContent = `${index + 1} / ${images.length}`;
renderThumbnails();
scrollToThumb(index);
}
function navigateImage(dir) {
let newIdx = currentIndex + dir;
if (newIdx < 0) newIdx = images.length - 1;
if (newIdx >= images.length) newIdx = 0;
updateMainImage(newIdx);
}
function setupZoom() {
const container = document.getElementById('mainImageContainer');
container.addEventListener('click', (e) => {
if (e.target.closest('button')) return;
isZoomed = !isZoomed;
const img = document.getElementById('mainImage');
img.style.transform = isZoomed ? 'scale(2)' : 'scale(1)';
img.style.cursor = isZoomed ? 'zoom-out' : 'zoom-in';
container.style.overflow = isZoomed ? 'scroll' : 'hidden';
const indicator = document.getElementById('zoomIndicator');
indicator.innerHTML = isZoomed ? '<i class="fas fa-search-minus"></i>' : '<i class="fas fa-search-plus"></i>';
});
container.addEventListener('dblclick', (e) => {
if (!e.target.closest('button')) openLightbox();
});
}
function scrollThumbnails(dir) {
const wrapper = document.getElementById('thumbnailsWrapper');
const scrollAmount = wrapper.offsetWidth * 0.6;
thumbScrollPos += dir * scrollAmount;
const track = document.getElementById('thumbnailsTrack');
const maxScroll = track.scrollWidth - wrapper.offsetWidth;
thumbScrollPos = Math.max(0, Math.min(thumbScrollPos, maxScroll));
track.style.transform = `translateX(-${thumbScrollPos}px)`;
updateThumbButtons(maxScroll);
}
function scrollToThumb(index) {
const wrapper = document.getElementById('thumbnailsWrapper');
const thumbWidth = wrapper.offsetWidth / 5;
thumbScrollPos = index * thumbWidth - thumbWidth;
const track = document.getElementById('thumbnailsTrack');
const maxScroll = track.scrollWidth - wrapper.offsetWidth;
thumbScrollPos = Math.max(0, Math.min(thumbScrollPos, maxScroll));
track.style.transform = `translateX(-${thumbScrollPos}px)`;
updateThumbButtons(maxScroll);
}
function updateThumbButtons(maxScroll) {
document.getElementById('prevThumbBtn').disabled = thumbScrollPos <= 0;
document.getElementById('nextThumbBtn').disabled = thumbScrollPos >= maxScroll - 1;
}
function openLightbox() {
document.getElementById('lightboxModal').classList.remove('hidden');
document.getElementById('lightboxModal').classList.add('flex');
document.body.style.overflow = 'hidden';
updateLightbox();
}
function closeLightbox() {
document.getElementById('lightboxModal').classList.add('hidden');
document.getElementById('lightboxModal').classList.remove('flex');
document.body.style.overflow = '';
}
function updateLightbox() {
document.getElementById('lightboxImage').src = images[currentIndex].src;
document.getElementById('lightboxCounter').textContent = `${currentIndex + 1} / ${images.length}`;
}
function navigateLightbox(dir) {
let newIdx = currentIndex + dir;
if (newIdx < 0) newIdx = images.length - 1;
if (newIdx >= images.length) newIdx = 0;
updateMainImage(newIdx);
updateLightbox();
}
function setupKeyboard() {
document.addEventListener('keydown', (e) => {
const modal = document.getElementById('lightboxModal');
if (modal.classList.contains('flex')) {
if (e.key === 'Escape') closeLightbox();
if (e.key === 'ArrowLeft') navigateLightbox(-1);
if (e.key === 'ArrowRight') navigateLightbox(1);
}
});
}
document.getElementById('lightboxModal').addEventListener('click', (e) => {
if (e.target === document.getElementById('lightboxModal')) closeLightbox();
});
document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>
Télécharger le fichier source