Member
Account Settings
Share AgencyGenius
Know someone who might benefit from AgencyGenius? We'd love it if you shared our platform with your networking group!

Documentation
Step 1: Add HTML, Css & JavaScript
<!-- PARALLAX IMAGE SLIDER WITH THUMBNAILS A vanilla JavaScript implementation of a parallax image slider with thumbnail navigation. This version is optimized for mobile responsiveness while maintaining the original design. --> <div id="macos-parallax-slider-widget" class="img-slider"> <div class="img-slider__list"> <div data-slideshow="slide" class="img-slide is--current"> <img data-slideshow="parallax" alt="macOS Sonoma Wallpaper" src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/macos-sonoma-wallpapers-5120x2160-v0-j9vwvbq8h5wb1.jpg" draggable="false" class="img-slide__inner"> </div> <div data-slideshow="slide" class="img-slide"> <img data-slideshow="parallax" alt="macOS Mojave Day Wallpaper" src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/macOS-Mojave-Day-wallpaper.jpg" draggable="false" class="img-slide__inner"> </div> <div data-slideshow="slide" class="img-slide"> <img data-slideshow="parallax" alt="macOS Abstract Wallpaper" src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/da68i50-00fbde21-b0fa-49cc-a0ca-cdebca7b0730.jpg" draggable="false" class="img-slide__inner"> </div> <div data-slideshow="slide" class="img-slide"> <img data-slideshow="parallax" alt="macOS Big Sur Wallpaper" src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/macos-big-sur-ysfrml6k0il45zd8.jpg" draggable="false" class="img-slide__inner"> </div> <div data-slideshow="slide" class="img-slide"> <img data-slideshow="parallax" alt="Mountain Scenery macOS Wallpaper 1" src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/mountain-scenery-macos-4k-wallpaper-uhdpaper.com-106-0-h.jpg" draggable="false" class="img-slide__inner"> </div> <div data-slideshow="slide" class="img-slide"> <img data-slideshow="parallax" alt="Mountain Scenery macOS Wallpaper 2" src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/mountain-scenery-macos-4k-wallpaper-uhdpaper.com-108-0-h.jpg" draggable="false" class="img-slide__inner"> </div> </div> <div class="img-slider__nav"> <div data-slideshow="thumb" class="img-slider__thumb is--current"> <img src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/macos-sonoma-wallpapers-5120x2160-v0-j9vwvbq8h5wb1.jpg" class="slider-thumb__img"> </div> <div data-slideshow="thumb" class="img-slider__thumb"> <img src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/macOS-Mojave-Day-wallpaper.jpg" class="slider-thumb__img"> </div> <div data-slideshow="thumb" class="img-slider__thumb"> <img src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/da68i50-00fbde21-b0fa-49cc-a0ca-cdebca7b0730.jpg" class="slider-thumb__img"> </div> <div data-slideshow="thumb" class="img-slider__thumb"> <img src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/macos-big-sur-ysfrml6k0il45zd8.jpg" class="slider-thumb__img"> </div> <div data-slideshow="thumb" class="img-slider__thumb"> <img src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/mountain-scenery-macos-4k-wallpaper-uhdpaper.com-106-0-h.jpg" class="slider-thumb__img"> </div> <div data-slideshow="thumb" class="img-slider__thumb"> <img src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/mountain-scenery-macos-4k-wallpaper-uhdpaper.com-108-0-h.jpg" class="slider-thumb__img"> </div> </div> </div> <!-- End of PARALLAX IMAGE SLIDER WITH THUMBNAILS -->
/* Base slider styling */ #macos-parallax-slider-widget { --transition-duration: 900ms; /* This custom bezier curve closely matches the GSAP "slideshow-wipe" custom ease */ --transition-easing: cubic-bezier(0.6, 0.08, 0.02, 0.99); grid-column-gap: 1rem; grid-row-gap: 1rem; border-radius: .5em; justify-content: center; align-items: flex-end; width: 100%; /* Use aspect ratio instead of fixed height for better responsiveness */ aspect-ratio: 16/9; max-height: 100vh; display: flex; position: relative; } #macos-parallax-slider-widget .img-slider__list { grid-template-rows: 100%; grid-template-columns: 100%; place-items: center; width: 100%; height: 100%; display: grid; overflow: hidden; position: relative; } #macos-parallax-slider-widget .img-slide { opacity: 0; pointer-events: none; grid-area: 1 / 1 / -1 / -1; place-items: center; width: 100%; height: 100%; display: grid; position: absolute; top: 0; left: 0; overflow: hidden; will-change: transform, opacity; /* Start positioned off-screen */ transform: translateX(100%); z-index: 1; } #macos-parallax-slider-widget .img-slide.is--current { opacity: 1; pointer-events: auto; transform: translateX(0); z-index: 2; } #macos-parallax-slider-widget .img-slide.is--previous { z-index: 1; opacity: 1; } #macos-parallax-slider-widget .img-slide__inner { object-fit: cover; width: 100%; height: 100%; position: absolute; will-change: transform; } #macos-parallax-slider-widget .img-slider__nav { z-index: 10; grid-column-gap: .5rem; grid-row-gap: .5rem; pointer-events: none; flex-flow: wrap; justify-content: center; align-items: center; max-width: 95vw; display: flex; position: absolute; bottom: 2rem; } #macos-parallax-slider-widget .img-slider__thumb { aspect-ratio: 1.5; pointer-events: auto; cursor: pointer; border: 1px solid rgba(255, 255, 255, 0.2); border-radius: .3125rem; width: 7rem; transition: border-color .2s; position: relative; overflow: hidden; } #macos-parallax-slider-widget .img-slider__thumb:hover { border-color: rgba(255, 255, 255, 0.4); } #macos-parallax-slider-widget .img-slider__thumb.is--current { border-color: #fff; } #macos-parallax-slider-widget .slider-thumb__img { object-fit: cover; width: 100%; height: 100%; } /* Auto-transition for slider */ @keyframes slider-auto { 0%, 85% { opacity: 1; } 100% { opacity: 1; } } /* Responsive adjustments */ @media screen and (max-width: 991px) { #macos-parallax-slider-widget { /* Slightly adjust aspect ratio for tablets */ aspect-ratio: 4/3; } #macos-parallax-slider-widget .img-slider__list { width: 100%; } #macos-parallax-slider-widget .img-slider__thumb { flex: none; } } @media screen and (max-width: 767px) { #macos-parallax-slider-widget { /* More compact aspect ratio for mobile */ aspect-ratio: 3/4; } #macos-parallax-slider-widget .img-slider__nav { flex-flow: wrap; /* Adjust bottom position on mobile */ bottom: 1rem; } #macos-parallax-slider-widget .img-slider__thumb { border-radius: .25rem; width: 5rem; } } @media screen and (max-width: 479px) { #macos-parallax-slider-widget .img-slider__thumb { width: 4.5rem; } } /* Handle landscape orientation on mobile */ @media screen and (max-width: 767px) and (orientation: landscape) { #macos-parallax-slider-widget { /* Return to wider aspect ratio for landscape orientation */ aspect-ratio: 16/9; } #macos-parallax-slider-widget .img-slider__nav { bottom: 0.5rem; } #macos-parallax-slider-widget .img-slider__thumb { width: 4rem; } }
// Wait for DOM to be fully loaded (function() { // Immediately-invoked function to avoid polluting global namespace function initParallaxSlider(sliderEl) { // Animation settings const ANIMATION_DURATION = 900; // Match CSS variable (in ms) const AUTO_SLIDE_INTERVAL = 5000; // Time between auto slides (in ms) let autoSlideTimer = null; // Flag to track if animation is in progress let animating = false; // Store all slider elements const ui = { el: sliderEl, container: sliderEl.querySelector('.img-slider__list'), slides: Array.from(sliderEl.querySelectorAll('[data-slideshow="slide"]')), inner: Array.from(sliderEl.querySelectorAll('[data-slideshow="parallax"]')), thumbs: Array.from(sliderEl.querySelectorAll('[data-slideshow="thumb"]')) }; // Track current slide index let current = 0; const totalSlides = ui.slides.length; // Add index attributes for easier reference ui.slides.forEach((slide, index) => { slide.setAttribute('data-index', index); }); ui.thumbs.forEach((thumb, index) => { thumb.setAttribute('data-index', index); }); // Initialize the slider function initialize() { // Set initial slide ui.slides[current].classList.add('is--current'); ui.thumbs[current].classList.add('is--current'); // Set up event listeners setupThumbnailListeners(); setupGestureListeners(); // Start auto-sliding if more than one slide if (ui.slides.length > 1) { startAutoSlide(); } } // Start auto-sliding functionality function startAutoSlide() { if (autoSlideTimer) { clearInterval(autoSlideTimer); } autoSlideTimer = setInterval(() => { if (!animating) { navigate(1); } }, AUTO_SLIDE_INTERVAL); } // Pause auto-sliding (on user interaction) function pauseAutoSlide() { if (autoSlideTimer) { clearInterval(autoSlideTimer); autoSlideTimer = null; } } // Resume auto-sliding (after user interaction) function resumeAutoSlide() { pauseAutoSlide(); startAutoSlide(); } // Handle navigation between slides function navigate(direction, targetIndex = null) { if (animating) return; animating = true; // Pause auto-sliding during manual navigation pauseAutoSlide(); // Store previous slide index const previous = current; // Calculate new slide index if (targetIndex !== null && targetIndex !== undefined) { current = targetIndex; direction = targetIndex > previous ? 1 : -1; } else { current = direction === 1 ? (current < totalSlides - 1 ? current + 1 : 0) : (current > 0 ? current - 1 : totalSlides - 1); } // Get slide elements const prevSlide = ui.slides[previous]; const prevInner = ui.inner[previous]; const nextSlide = ui.slides[current]; const nextInner = ui.inner[current]; // Mark previous slide prevSlide.classList.add('is--previous'); // Reset all slides not involved in transition ui.slides.forEach((slide, idx) => { if (idx !== previous && idx !== current) { slide.style.transform = 'translateX(100%)'; slide.classList.remove('is--current', 'is--previous'); ui.inner[idx].style.transform = ''; } }); // Animation steps using requestAnimationFrame for smoother transitions // Step 1: Setup starting positions if (direction === 1) { // Going forward: current slide moves left, next slide comes from right nextSlide.style.transform = 'translateX(100%)'; nextInner.style.transform = 'translateX(-50%)'; } else { // Going backward: current slide moves right, next slide comes from left nextSlide.style.transform = 'translateX(-100%)'; nextInner.style.transform = 'translateX(50%)'; } // Update thumbnail indicators immediately ui.thumbs[previous].classList.remove('is--current'); ui.thumbs[current].classList.add('is--current'); // Step 2: Apply transitions requestAnimationFrame(() => { // Apply transition styles applyTransitionStyles(prevSlide); applyTransitionStyles(prevInner); applyTransitionStyles(nextSlide); applyTransitionStyles(nextInner); // Force a reflow to ensure transitions take effect void nextSlide.offsetWidth; // Step 3: Move to final positions requestAnimationFrame(() => { // Move the slides to their final positions if (direction === 1) { prevSlide.style.transform = 'translateX(-100%)'; prevInner.style.transform = 'translateX(50%)'; } else { prevSlide.style.transform = 'translateX(100%)'; prevInner.style.transform = 'translateX(-50%)'; } // Bring new slide into view nextSlide.style.transform = 'translateX(0)'; nextInner.style.transform = 'translateX(0)'; // Add current class to make it visible nextSlide.classList.add('is--current'); // Handle cleanup after transition completes setTimeout(() => { // Remove transition styles removeTransitionStyles(prevSlide); removeTransitionStyles(prevInner); removeTransitionStyles(nextSlide); removeTransitionStyles(nextInner); // Reset previous slide prevSlide.classList.remove('is--current', 'is--previous'); // Re-enable interactions animating = false; // Resume auto-sliding resumeAutoSlide(); }, ANIMATION_DURATION); }); }); } // Apply transition styles to an element function applyTransitionStyles(element) { element.style.transition = `transform ${ANIMATION_DURATION}ms var(--transition-easing)`; } // Remove transition styles from an element function removeTransitionStyles(element) { element.style.transition = ''; } // Set up click handlers for thumbnails function setupThumbnailListeners() { ui.thumbs.forEach(thumb => { thumb.addEventListener('click', handleThumbClick); }); } // Handle thumbnail click function handleThumbClick(event) { const targetIndex = parseInt(event.currentTarget.getAttribute('data-index'), 10); if (targetIndex === current || animating) return; const direction = targetIndex > current ? 1 : -1; navigate(direction, targetIndex); } // Touch and wheel gesture handling function setupGestureListeners() { // Variables for touch tracking let touchStartX = 0; let touchEndX = 0; const minSwipeDistance = 50; // Pause auto-sliding on user interaction sliderEl.addEventListener('mouseenter', pauseAutoSlide); sliderEl.addEventListener('mouseleave', resumeAutoSlide); sliderEl.addEventListener('touchstart', pauseAutoSlide, { passive: true }); // Touch events for mobile sliderEl.addEventListener('touchstart', function(e) { touchStartX = e.changedTouches[0].screenX; }, { passive: true }); sliderEl.addEventListener('touchend', function(e) { if (animating) return; touchEndX = e.changedTouches[0].screenX; handleSwipe(); // Resume auto-sliding after touch setTimeout(resumeAutoSlide, 500); }, { passive: true }); // Mouse wheel events sliderEl.addEventListener('wheel', function(e) { if (animating) return; // Only respond to horizontal wheel events if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) { // Prevent page scrolling e.preventDefault(); if (e.deltaX > minSwipeDistance) { navigate(1); } else if (e.deltaX < -minSwipeDistance) { navigate(-1); } } }, { passive: false }); // Process swipe direction function handleSwipe() { const swipeDistance = touchEndX - touchStartX; if (Math.abs(swipeDistance) > minSwipeDistance) { if (swipeDistance < 0) { // Swipe left, go to next navigate(1); } else { // Swipe right, go to previous navigate(-1); } } } } // Initialize the slider initialize(); // Return public methods for external control return { goTo: function(index) { if (index >= 0 && index < totalSlides && index !== current) { const direction = index > current ? 1 : -1; navigate(direction, index); } }, next: function() { navigate(1); }, prev: function() { navigate(-1); }, pause: pauseAutoSlide, resume: resumeAutoSlide }; } // Initialize all sliders when DOM is ready document.addEventListener("DOMContentLoaded", function() { const sliders = document.querySelectorAll('#macos-parallax-slider-widget'); sliders.forEach(slider => { const sliderInstance = initParallaxSlider(slider); // Store the slider instance on the element for external access slider.sliderAPI = sliderInstance; }); }); })();
<div id="snippet-o2yykqou"> <!-- HTML SECTION START --> <!-- PARALLAX IMAGE SLIDER WITH THUMBNAILS A vanilla JavaScript implementation of a parallax image slider with thumbnail navigation. This version is optimized for mobile responsiveness while maintaining the original design. --> <div id="macos-parallax-slider-widget" class="img-slider"> <div class="img-slider__list"> <div data-slideshow="slide" class="img-slide is--current"> <img data-slideshow="parallax" alt="macOS Sonoma Wallpaper" src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/macos-sonoma-wallpapers-5120x2160-v0-j9vwvbq8h5wb1.jpg" draggable="false" class="img-slide__inner"> </div> <div data-slideshow="slide" class="img-slide"> <img data-slideshow="parallax" alt="macOS Mojave Day Wallpaper" src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/macOS-Mojave-Day-wallpaper.jpg" draggable="false" class="img-slide__inner"> </div> <div data-slideshow="slide" class="img-slide"> <img data-slideshow="parallax" alt="macOS Abstract Wallpaper" src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/da68i50-00fbde21-b0fa-49cc-a0ca-cdebca7b0730.jpg" draggable="false" class="img-slide__inner"> </div> <div data-slideshow="slide" class="img-slide"> <img data-slideshow="parallax" alt="macOS Big Sur Wallpaper" src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/macos-big-sur-ysfrml6k0il45zd8.jpg" draggable="false" class="img-slide__inner"> </div> <div data-slideshow="slide" class="img-slide"> <img data-slideshow="parallax" alt="Mountain Scenery macOS Wallpaper 1" src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/mountain-scenery-macos-4k-wallpaper-uhdpaper.com-106-0-h.jpg" draggable="false" class="img-slide__inner"> </div> <div data-slideshow="slide" class="img-slide"> <img data-slideshow="parallax" alt="Mountain Scenery macOS Wallpaper 2" src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/mountain-scenery-macos-4k-wallpaper-uhdpaper.com-108-0-h.jpg" draggable="false" class="img-slide__inner"> </div> </div> <div class="img-slider__nav"> <div data-slideshow="thumb" class="img-slider__thumb is--current"> <img src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/macos-sonoma-wallpapers-5120x2160-v0-j9vwvbq8h5wb1.jpg" class="slider-thumb__img"> </div> <div data-slideshow="thumb" class="img-slider__thumb"> <img src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/macOS-Mojave-Day-wallpaper.jpg" class="slider-thumb__img"> </div> <div data-slideshow="thumb" class="img-slider__thumb"> <img src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/da68i50-00fbde21-b0fa-49cc-a0ca-cdebca7b0730.jpg" class="slider-thumb__img"> </div> <div data-slideshow="thumb" class="img-slider__thumb"> <img src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/macos-big-sur-ysfrml6k0il45zd8.jpg" class="slider-thumb__img"> </div> <div data-slideshow="thumb" class="img-slider__thumb"> <img src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/mountain-scenery-macos-4k-wallpaper-uhdpaper.com-106-0-h.jpg" class="slider-thumb__img"> </div> <div data-slideshow="thumb" class="img-slider__thumb"> <img src="https://irp.cdn-website.com/a778beb9/dms3rep/multi/mountain-scenery-macos-4k-wallpaper-uhdpaper.com-108-0-h.jpg" class="slider-thumb__img"> </div> </div> </div> <!-- End of PARALLAX IMAGE SLIDER WITH THUMBNAILS --> <!-- HTML SECTION END --> <!-- CSS SECTION START --> <style> /* Base slider styling */ #macos-parallax-slider-widget { --transition-duration: 900ms; /* This custom bezier curve closely matches the GSAP "slideshow-wipe" custom ease */ --transition-easing: cubic-bezier(0.6, 0.08, 0.02, 0.99); grid-column-gap: 1rem; grid-row-gap: 1rem; border-radius: .5em; justify-content: center; align-items: flex-end; width: 100%; /* Use aspect ratio instead of fixed height for better responsiveness */ aspect-ratio: 16/9; max-height: 100vh; display: flex; position: relative; } #macos-parallax-slider-widget .img-slider__list { grid-template-rows: 100%; grid-template-columns: 100%; place-items: center; width: 100%; height: 100%; display: grid; overflow: hidden; position: relative; } #macos-parallax-slider-widget .img-slide { opacity: 0; pointer-events: none; grid-area: 1 / 1 / -1 / -1; place-items: center; width: 100%; height: 100%; display: grid; position: absolute; top: 0; left: 0; overflow: hidden; will-change: transform, opacity; /* Start positioned off-screen */ transform: translateX(100%); z-index: 1; } #macos-parallax-slider-widget .img-slide.is--current { opacity: 1; pointer-events: auto; transform: translateX(0); z-index: 2; } #macos-parallax-slider-widget .img-slide.is--previous { z-index: 1; opacity: 1; } #macos-parallax-slider-widget .img-slide__inner { object-fit: cover; width: 100%; height: 100%; position: absolute; will-change: transform; } #macos-parallax-slider-widget .img-slider__nav { z-index: 10; grid-column-gap: .5rem; grid-row-gap: .5rem; pointer-events: none; flex-flow: wrap; justify-content: center; align-items: center; max-width: 95vw; display: flex; position: absolute; bottom: 2rem; } #macos-parallax-slider-widget .img-slider__thumb { aspect-ratio: 1.5; pointer-events: auto; cursor: pointer; border: 1px solid rgba(255, 255, 255, 0.2); border-radius: .3125rem; width: 7rem; transition: border-color .2s; position: relative; overflow: hidden; } #macos-parallax-slider-widget .img-slider__thumb:hover { border-color: rgba(255, 255, 255, 0.4); } #macos-parallax-slider-widget .img-slider__thumb.is--current { border-color: #fff; } #macos-parallax-slider-widget .slider-thumb__img { object-fit: cover; width: 100%; height: 100%; } /* Auto-transition for slider */ @keyframes slider-auto { 0%, 85% { opacity: 1; } 100% { opacity: 1; } } /* Responsive adjustments */ @media screen and (max-width: 991px) { #macos-parallax-slider-widget { /* Slightly adjust aspect ratio for tablets */ aspect-ratio: 4/3; } #macos-parallax-slider-widget .img-slider__list { width: 100%; } #macos-parallax-slider-widget .img-slider__thumb { flex: none; } } @media screen and (max-width: 767px) { #macos-parallax-slider-widget { /* More compact aspect ratio for mobile */ aspect-ratio: 3/4; } #macos-parallax-slider-widget .img-slider__nav { flex-flow: wrap; /* Adjust bottom position on mobile */ bottom: 1rem; } #macos-parallax-slider-widget .img-slider__thumb { border-radius: .25rem; width: 5rem; } } @media screen and (max-width: 479px) { #macos-parallax-slider-widget .img-slider__thumb { width: 4.5rem; } } /* Handle landscape orientation on mobile */ @media screen and (max-width: 767px) and (orientation: landscape) { #macos-parallax-slider-widget { /* Return to wider aspect ratio for landscape orientation */ aspect-ratio: 16/9; } #macos-parallax-slider-widget .img-slider__nav { bottom: 0.5rem; } #macos-parallax-slider-widget .img-slider__thumb { width: 4rem; } } </style> <!-- CSS SECTION END --> <!-- JAVASCRIPT SECTION START --> <script> // Wait for DOM to be fully loaded (function() { // Immediately-invoked function to avoid polluting global namespace function initParallaxSlider(sliderEl) { // Animation settings const ANIMATION_DURATION = 900; // Match CSS variable (in ms) const AUTO_SLIDE_INTERVAL = 5000; // Time between auto slides (in ms) let autoSlideTimer = null; // Flag to track if animation is in progress let animating = false; // Store all slider elements const ui = { el: sliderEl, container: sliderEl.querySelector('.img-slider__list'), slides: Array.from(sliderEl.querySelectorAll('[data-slideshow="slide"]')), inner: Array.from(sliderEl.querySelectorAll('[data-slideshow="parallax"]')), thumbs: Array.from(sliderEl.querySelectorAll('[data-slideshow="thumb"]')) }; // Track current slide index let current = 0; const totalSlides = ui.slides.length; // Add index attributes for easier reference ui.slides.forEach((slide, index) => { slide.setAttribute('data-index', index); }); ui.thumbs.forEach((thumb, index) => { thumb.setAttribute('data-index', index); }); // Initialize the slider function initialize() { // Set initial slide ui.slides[current].classList.add('is--current'); ui.thumbs[current].classList.add('is--current'); // Set up event listeners setupThumbnailListeners(); setupGestureListeners(); // Start auto-sliding if more than one slide if (ui.slides.length > 1) { startAutoSlide(); } } // Start auto-sliding functionality function startAutoSlide() { if (autoSlideTimer) { clearInterval(autoSlideTimer); } autoSlideTimer = setInterval(() => { if (!animating) { navigate(1); } }, AUTO_SLIDE_INTERVAL); } // Pause auto-sliding (on user interaction) function pauseAutoSlide() { if (autoSlideTimer) { clearInterval(autoSlideTimer); autoSlideTimer = null; } } // Resume auto-sliding (after user interaction) function resumeAutoSlide() { pauseAutoSlide(); startAutoSlide(); } // Handle navigation between slides function navigate(direction, targetIndex = null) { if (animating) return; animating = true; // Pause auto-sliding during manual navigation pauseAutoSlide(); // Store previous slide index const previous = current; // Calculate new slide index if (targetIndex !== null && targetIndex !== undefined) { current = targetIndex; direction = targetIndex > previous ? 1 : -1; } else { current = direction === 1 ? (current < totalSlides - 1 ? current + 1 : 0) : (current > 0 ? current - 1 : totalSlides - 1); } // Get slide elements const prevSlide = ui.slides[previous]; const prevInner = ui.inner[previous]; const nextSlide = ui.slides[current]; const nextInner = ui.inner[current]; // Mark previous slide prevSlide.classList.add('is--previous'); // Reset all slides not involved in transition ui.slides.forEach((slide, idx) => { if (idx !== previous && idx !== current) { slide.style.transform = 'translateX(100%)'; slide.classList.remove('is--current', 'is--previous'); ui.inner[idx].style.transform = ''; } }); // Animation steps using requestAnimationFrame for smoother transitions // Step 1: Setup starting positions if (direction === 1) { // Going forward: current slide moves left, next slide comes from right nextSlide.style.transform = 'translateX(100%)'; nextInner.style.transform = 'translateX(-50%)'; } else { // Going backward: current slide moves right, next slide comes from left nextSlide.style.transform = 'translateX(-100%)'; nextInner.style.transform = 'translateX(50%)'; } // Update thumbnail indicators immediately ui.thumbs[previous].classList.remove('is--current'); ui.thumbs[current].classList.add('is--current'); // Step 2: Apply transitions requestAnimationFrame(() => { // Apply transition styles applyTransitionStyles(prevSlide); applyTransitionStyles(prevInner); applyTransitionStyles(nextSlide); applyTransitionStyles(nextInner); // Force a reflow to ensure transitions take effect void nextSlide.offsetWidth; // Step 3: Move to final positions requestAnimationFrame(() => { // Move the slides to their final positions if (direction === 1) { prevSlide.style.transform = 'translateX(-100%)'; prevInner.style.transform = 'translateX(50%)'; } else { prevSlide.style.transform = 'translateX(100%)'; prevInner.style.transform = 'translateX(-50%)'; } // Bring new slide into view nextSlide.style.transform = 'translateX(0)'; nextInner.style.transform = 'translateX(0)'; // Add current class to make it visible nextSlide.classList.add('is--current'); // Handle cleanup after transition completes setTimeout(() => { // Remove transition styles removeTransitionStyles(prevSlide); removeTransitionStyles(prevInner); removeTransitionStyles(nextSlide); removeTransitionStyles(nextInner); // Reset previous slide prevSlide.classList.remove('is--current', 'is--previous'); // Re-enable interactions animating = false; // Resume auto-sliding resumeAutoSlide(); }, ANIMATION_DURATION); }); }); } // Apply transition styles to an element function applyTransitionStyles(element) { element.style.transition = `transform ${ANIMATION_DURATION}ms var(--transition-easing)`; } // Remove transition styles from an element function removeTransitionStyles(element) { element.style.transition = ''; } // Set up click handlers for thumbnails function setupThumbnailListeners() { ui.thumbs.forEach(thumb => { thumb.addEventListener('click', handleThumbClick); }); } // Handle thumbnail click function handleThumbClick(event) { const targetIndex = parseInt(event.currentTarget.getAttribute('data-index'), 10); if (targetIndex === current || animating) return; const direction = targetIndex > current ? 1 : -1; navigate(direction, targetIndex); } // Touch and wheel gesture handling function setupGestureListeners() { // Variables for touch tracking let touchStartX = 0; let touchEndX = 0; const minSwipeDistance = 50; // Pause auto-sliding on user interaction sliderEl.addEventListener('mouseenter', pauseAutoSlide); sliderEl.addEventListener('mouseleave', resumeAutoSlide); sliderEl.addEventListener('touchstart', pauseAutoSlide, { passive: true }); // Touch events for mobile sliderEl.addEventListener('touchstart', function(e) { touchStartX = e.changedTouches[0].screenX; }, { passive: true }); sliderEl.addEventListener('touchend', function(e) { if (animating) return; touchEndX = e.changedTouches[0].screenX; handleSwipe(); // Resume auto-sliding after touch setTimeout(resumeAutoSlide, 500); }, { passive: true }); // Mouse wheel events sliderEl.addEventListener('wheel', function(e) { if (animating) return; // Only respond to horizontal wheel events if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) { // Prevent page scrolling e.preventDefault(); if (e.deltaX > minSwipeDistance) { navigate(1); } else if (e.deltaX < -minSwipeDistance) { navigate(-1); } } }, { passive: false }); // Process swipe direction function handleSwipe() { const swipeDistance = touchEndX - touchStartX; if (Math.abs(swipeDistance) > minSwipeDistance) { if (swipeDistance < 0) { // Swipe left, go to next navigate(1); } else { // Swipe right, go to previous navigate(-1); } } } } // Initialize the slider initialize(); // Return public methods for external control return { goTo: function(index) { if (index >= 0 && index < totalSlides && index !== current) { const direction = index > current ? 1 : -1; navigate(direction, index); } }, next: function() { navigate(1); }, prev: function() { navigate(-1); }, pause: pauseAutoSlide, resume: resumeAutoSlide }; } // Initialize all sliders when DOM is ready document.addEventListener("DOMContentLoaded", function() { const sliders = document.querySelectorAll('#macos-parallax-slider-widget'); sliders.forEach(slider => { const sliderInstance = initParallaxSlider(slider); // Store the slider instance on the element for external access slider.sliderAPI = sliderInstance; }); }); })(); </script> <!-- JAVASCRIPT SECTION END --> </div>
Implementation
The Parallax Image Slider creates an elegant, responsive gallery with a subtle parallax effect that enhances visual depth during transitions. When users navigate between slides, images shift slightly in the opposite direction of the transition, creating a sophisticated 3D-like effect. The widget features thumbnail navigation and works seamlessly across all device sizes.
HTML Structure
Use the following HTML structure:
- The main container img-slider holds all slider elements
- The img-slider__list contains all slide panels
- Each slide is a img-slide div containing a parallax image
- The first slide should have the is--current class applied initially
- Images with the data-slideshow="parallax" attribute enable the parallax effect
- Thumbnail navigation img-slider__nav contains small previews of each slide
- Each thumbnail is a img-slider__thumb div with matching image
CSS Styling
The CSS manages layout, transitions, and responsive behavior:
- Uses CSS variables for consistent settings:
- --transition-duration controls animation speed
- --transition-easing defines the custom timing function
- Employs aspect-ratio instead of fixed height for better mobile responsiveness
- Implements responsive breakpoints for different devices:
- Desktop: 16:9 aspect ratio with larger thumbnails
- Tablets (max-width: 991px): 4:3 aspect ratio
- Mobile portrait (max-width: 767px): 3:4 aspect ratio with smaller thumbnails
- Mobile landscape: returns to 16:9 aspect ratio with adjusted controls
- Uses CSS grid for slide positioning:
- grid-area: 1 / 1 / -1 / -1 stacks all slides in the same grid cell
- position: absolute on slides enables transitions
- Employs will-change: transform, opacity for performance optimization
JavaScript Functionality
The JavaScript handles all interactions and animations:
- Initialization: Sets up event listeners and starts autoplay
- Navigation: Handles transitions between slides with the parallax effect:
- For next/forward: Current slide moves left, inner image moves right
- For previous/backward: Current slide moves right, inner image moves left
- Animation Control:
- Uses requestAnimationFrame for smoother transitions
- Applies transition styles dynamically using applyTransitionStyles()
- Prevents multiple animations with the animating flag
- Touch Gesture Support:
- Detects swipe direction using touchstart and touchend events
- Handles horizontal scrolling with wheel event detection
- Autoplay:
- Automatically transitions slides at regular intervals
- Pauses on user interaction and resumes after a delay
Customization Tips
You can easily customize various aspects of the slider:
- Transition speed: Modify the --transition-duration CSS variable (default: 900ms)
- Easing curve: Adjust the --transition-easing value for different motion feels
- Autoplay timing: Change the AUTO_SLIDE_INTERVAL constant (default: 5000ms)
- Aspect ratios: Modify the aspect-ratio property in media queries for different proportions
- Thumbnail size: Adjust the width value for .img-slider__thumb
- Responsive breakpoints: Change the max-width values in media queries
- Parallax intensity: Alter the translateX percentage values in the navigate() function
CMS Integration
To integrate with a CMS:
- Create a collection for slider items with fields for:
- Slide image (high-resolution for the main slide)
- Thumbnail image (can be same as slide image but will be resized)
- Alt text for accessibility
- Implementation steps:
- Loop through your collection to generate both slides and thumbnails
- Ensure matching indexes between slides and thumbnails
- Add is--current class to the first slide and thumbnail
- Make sure to add proper data-slideshow attributes
- For multiple sliders on one page:
- Use unique IDs instead of #macos-parallax-slider-widget
- Initialize each slider separately in the JavaScript
- Use the returned API methods ( next() , prev() , goTo() ) to control each instance

How to edit code using AI →
Resource Details:
The Parallax Image Slider Widget creates a full-screen image slider with smooth parallax transitions between slides. It features thumbnail navigation, touch/swipe support, and auto-advancing slides. The parallax effect adds depth as images move at different speeds during transitions, all without requiring external libraries like GSAP.

NO-ACCESS
Only for Desktop Users
This page is only available on desktop.
Please Log in on a desktop to gain access.