Galerie Lightbox Thumbnails Bootstrap 5

Extraits & Composants HTML 08/04/2026 13:00:00 angularforall.com
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

Partager