Product Thumbnail Slider With Zoom Effect Jquery Codepen -
// Set main image and reset zoom function setActiveImage(index) if (index === currentIndex) return; currentIndex = index; const newLargeSrc = galleryItems[currentIndex].large; // Reset zoom before changing image (avoid weird transforms) resetZoomWithGSAP(); // Fade transition effect gsap.to($mainImg[0], duration: 0.15, opacity: 0, onComplete: () => $mainImg.attr('src', newLargeSrc); $mainImg.attr('alt', galleryItems[currentIndex].alt); gsap.to($mainImg[0], duration: 0.2, opacity: 1 ); ); updateActiveThumbnail(); // also reset any ongoing zoom flag currentZoomScale = 1; $mainImg.css('transform', 'scale(1)');
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <title>Product Thumbnail Slider with Zoom Effect | jQuery & GSAP</title> <!-- Google Fonts & Font Awesome for clean icons --> <link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,300;14..32,400;14..32,500;14..32,600&display=swap" rel="stylesheet"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"> <!-- jQuery & GSAP for smooth zoom and slider --> <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script> <style> * margin: 0; padding: 0; box-sizing: border-box;
.thumb-img width: 100%; height: 100%; object-fit: cover; display: block;
/* main image container with zoom effect */ .zoom-container position: relative; width: 100%; background: #fff; border-radius: 1.8rem; overflow: hidden; box-shadow: 0 20px 35px -10px rgba(0,0,0,0.15); cursor: crosshair; aspect-ratio: 4 / 3; display: flex; align-items: center; justify-content: center; product thumbnail slider with zoom effect jquery codepen
/* smooth focus */ button:focus-visible outline: 2px solid #2c5f8a; </style> </head> <body>
body background: linear-gradient(145deg, #f5f7fc 0%, #eef2f6 100%); font-family: 'Inter', sans-serif; min-height: 100vh; display: flex; justify-content: center; align-items: center; padding: 2rem;
.thumbnail-track display: flex; gap: 1rem; padding: 0.2rem 0.2rem; // Set main image and reset zoom function
<!-- Thumbnail slider with arrows --> <div class="slider-section"> <div class="slider-header"> <span class="slider-title"><i class="fas fa-images"></i> Product gallery</span> <div class="nav-buttons"> <button class="nav-btn" id="prevThumbBtn" aria-label="Previous"><i class="fas fa-chevron-left"></i></button> <button class="nav-btn" id="nextThumbBtn" aria-label="Next"><i class="fas fa-chevron-right"></i></button> </div> </div> <div class="thumbnail-track-wrapper" id="thumbWrapper"> <div class="thumbnail-track" id="thumbTrack"> <!-- dynamic thumbnails will be injected via JS --> </div> </div> </div> </div> </div>
<div class="product-showcase"> <div class="product-grid"> <!-- Zoomable main area --> <div class="zoom-container" id="zoomContainer"> <img class="main-image" id="mainImage" src="https://picsum.photos/id/20/800/600" alt="Product main view"> <div class="zoom-badge"> <i class="fas fa-search-plus"></i> Hover to zoom </div> </div>
/* Thumbnail slider area */ .slider-section margin-top: 0.5rem; let mouseX = 0
.thumbnail-track-wrapper::-webkit-scrollbar-track background: #e2e8f0; border-radius: 10px;
// Current active index let currentIndex = 0; let currentZoomScale = 1; let isZooming = false; let zoomTween = null; // GSAP tween reference
.thumbnail-track-wrapper::-webkit-scrollbar-thumb background: #9aaec0; border-radius: 10px;
/* slider track */ .thumbnail-track-wrapper overflow-x: auto; scroll-behavior: smooth; scrollbar-width: thin; border-radius: 1.5rem; padding-bottom: 0.5rem;
// ----- ZOOM EFFECT LOGIC (with GSAP smooth scaling & following mouse) ----- // We implement a "magnifying zoom" that follows the mouse cursor on container hover. // Instead of lens, we scale the image and translate based on relative mouse position. // This gives an elegant zoom effect similar to product zoom. let mouseX = 0.5, mouseY = 0.5; let isHovering = false; const ZOOM_FACTOR = 2.4; // 2.4x zoom // Update transform based on mouse position and scale function updateZoomTransform(scale, mouseRelX, mouseRelY) scale <= 1) $mainImg.css( transform: `scale($scale)`, transformOrigin: "center center" ); return; // When zoomed, we shift the image so that the point under cursor stays under cursor // formula: translateX = (cursorX% - 0.5) * (scale-1) * width factor negative // Using percentages: transform-origin would be tricky, but we can set translate on top of scale. const translateX = (0.5 - mouseRelX) * (scale - 1) * 100; const translateY = (0.5 - mouseRelY) * (scale - 1) * 100; $mainImg.css( transform: `scale($scale) translate($translateX%, $translateY%)`, transformOrigin: "center center" ); // Mouse move inside container: get relative coordinates (0 to 1) function onMouseMove(e) if (!isHovering) return; const rect = $zoomContainer[0].getBoundingClientRect(); let offsetX = e.clientX - rect.left; let offsetY = e.clientY - rect.top; let relX = Math.min(Math.max(offsetX / rect.width, 0), 1); let relY = Math.min(Math.max(offsetY / rect.height, 0), 1); mouseX = relX; mouseY = relY; if (currentZoomScale > 1.05) updateZoomTransform(currentZoomScale, mouseX, mouseY); // Enter zoom zone: animate scale up function onZoomEnter() { if (zoomTween) zoomTween.kill(); isHovering = true; // smooth zoom in with GSAP zoomTween = gsap.to({}, duration: 0.28, ease: "power2.out", onUpdate: function() const progress = this.progress(); // 0->1 currentZoomScale = 1 + (ZOOM_FACTOR - 1) * progress; updateZoomTransform(currentZoomScale, mouseX, mouseY); , onComplete: () => currentZoomScale = ZOOM_FACTOR; updateZoomTransform(currentZoomScale, mouseX, mouseY); ); } function onZoomLeave() { if (!isHovering) return; isHovering = false; if (zoomTween) zoomTween.kill(); // smooth zoom out zoomTween = gsap.to({}, duration: 0.25, ease: "power2.in", onUpdate: function() const progress = this.progress(); // 0->1 currentZoomScale = ZOOM_FACTOR * (1 - progress) + 1 * progress; updateZoomTransform(currentZoomScale, mouseX, mouseY); , onComplete: () => currentZoomScale = 1; updateZoomTransform(1, mouseX, mouseY); $mainImg.css('transform', 'scale(1) translate(0%, 0%)'); ); } // Attach zoom events $zoomContainer.on('mouseenter', onZoomEnter); $zoomContainer.on('mouseleave', onZoomLeave); $zoomContainer.on('mousemove', onMouseMove); // Prevent zoom container from flickering when leaving image edge $zoomContainer.css( cursor: 'zoom-in' ); // Optional: reset zoom on window resize or when active image changes we also reset // Also reset zoom state when image changes const originalSetActive = setActiveImage; window.setActiveImage = function(index) if (isHovering) onZoomLeave(); // force exit zoom gracefully originalSetActive(index); // after image loads, reset zoom transform and variables setTimeout(() => currentZoomScale = 1; $mainImg.css('transform', 'scale(1) translate(0%, 0%)'); isHovering = false; , 30); ; setActiveImage = function(index) if (isHovering) onZoomLeave(); originalSetActive(index); setTimeout(() => currentZoomScale = 1; $mainImg.css('transform', 'scale(1) translate(0%, 0%)'); isHovering = false; , 30); ; // Override global reference window.setActiveImage = setActiveImage; // ---------- SLIDER LOGIC (next/prev + infinite scroll with wrapping) ---------- function nextSlide() let newIndex = currentIndex + 1; if (newIndex >= galleryItems.length) newIndex = 0; setActiveImage(newIndex); function prevSlide() let newIndex = currentIndex - 1; if (newIndex < 0) newIndex = galleryItems.length - 1; setActiveImage(newIndex); // Attach events for slider buttons prevBtn.on('click', prevSlide); nextBtn.on('click', nextSlide); // Keyboard support (left/right arrows) $(document).on('keydown', function(e) if (e.key === 'ArrowLeft') prevSlide(); e.preventDefault(); else if (e.key === 'ArrowRight') nextSlide(); e.preventDefault(); else if (e.key === 'Escape') if (isHovering) onZoomLeave(); ); // Preload images for better experience function preloadImages() galleryItems.forEach(item => const img = new Image(); img.src = item.large; ); // Initialize everything function init() buildThumbnails(); // Set first main image large resolution $mainImg.attr('src', galleryItems[0].large); $mainImg.attr('alt', galleryItems[0].alt); currentIndex = 0; updateActiveThumbnail(); preloadImages(); // Reset any leftover transforms resetZoomWithGSAP(); // additional: ensure mouse leave cleans $zoomContainer.on('touchstart', function(e) // For mobile: disable zoom effect because hover not ideal, we fallback to tap and zoom? but still fine, just prevent weirdness // On touch devices, we don't want zoom to block, but still you can use slider. We'll disable zoom on touch to avoid half-state if (isHovering) onZoomLeave(); ); init(); // Add extra responsive handling: when window resets, recalc zoom boundaries let resizeTimer; $(window).on('resize', function() if (resizeTimer) clearTimeout(resizeTimer); resizeTimer = setTimeout(() => if (isHovering) // if zoom active, force smooth re-alignment on resize const rect = $zoomContainer[0].getBoundingClientRect(); // keep relative mouse within container if possible, but just update transform if (currentZoomScale > 1) updateZoomTransform(currentZoomScale, mouseX, mouseY); , 100); ); // tiny console info for codepen context console.log('✅ Product Thumbnail Slider with ZOOM effect | jQuery + GSAP | Active'); </script> </body> </html>