/** * TouchSwiper - A minimal touch/click swipe carousel that works with your HTML structure * Now with proper handling of links during swipe actions */ (function() { function TouchSwiper(containerSelector, options) { // Find all matching containers var allContainers = document.querySelectorAll(containerSelector); // Initialize each container for (var i = 0; i < allContainers.length; i++) { initSingleSwiper(allContainers[i], options); } // Function to initialize a single swiper instance function initSingleSwiper(container, options) { var wrapper = container.querySelector('.swiper-wrapper-accessories'); var slides = Array.from(container.querySelectorAll('.swiper-slide-accessories')); if (!wrapper || !slides.length) return; // Default options var swiperOptions = { slidesPerView: 4, spaceBetween: 2, slidesPerGroup: 1, // Move by 1 slide at a time for smoother swiping loop: true, animationSpeed: 300, // ms breakpoints: { 1: { slidesPerView: 2 }, 600: { slidesPerView: 3 }, 900: { slidesPerView: 4 }, 1200: { slidesPerView: 5 }, 1500: { slidesPerView: 6 } } }; // Merge with user options if (options) { for (var key in options) { if (options.hasOwnProperty(key)) { if (typeof options[key] === 'object' && swiperOptions[key]) { for (var subKey in options[key]) { if (options[key].hasOwnProperty(subKey)) { swiperOptions[key][subKey] = options[key][subKey]; } } } else { swiperOptions[key] = options[key]; } } } } // State variables for this swiper var currentIndex = 0; var slideWidth = 0; var slidesPerView = swiperOptions.slidesPerView; var slidesPerGroup = swiperOptions.slidesPerGroup; var dragging = false; var startX = 0; var startY = 0; var currentX = 0; var dragDistance = 0; var clickAllowed = true; var moveThreshold = 5; // Minimum pixels moved to consider it a drag vs a click var resizeTimer; var transitionTimer; // Initialize function init() { // Apply responsive settings applyBreakpoints(); // Set up container styles container.style.position = 'relative'; container.style.overflow = 'hidden'; // Set up wrapper styles wrapper.style.display = 'flex'; wrapper.style.transition = 'transform ' + swiperOptions.animationSpeed + 'ms ease'; wrapper.style.willChange = 'transform'; // Calculate slide sizes calculateSizes(); // Create clones if looping if (swiperOptions.loop) { createClones(); } // Add event listeners addTouchListeners(); addMouseListeners(); addLinkClickHandlers(); // Add resize handler window.addEventListener('resize', onResize); // Go to initial position goToSlide(swiperOptions.loop ? slidesPerView : 0, false); } function addLinkClickHandlers() { // Find all links within slides var links = container.querySelectorAll('.swiper-slide-accessories a'); // Prevent default on links during drag links.forEach(function(link) { link.addEventListener('click', function(e) { if (!clickAllowed) { e.preventDefault(); e.stopPropagation(); } }); }); } function applyBreakpoints() { var windowWidth = window.innerWidth; var breakpoints = swiperOptions.breakpoints; if (breakpoints) { // Sort breakpoints var points = Object.keys(breakpoints).map(Number).sort(function(a, b) { return a - b; }); // Find and apply active breakpoint for (var i = 0; i < points.length; i++) { if (windowWidth >= points[i]) { var settings = breakpoints[points[i]]; if (settings.slidesPerView) slidesPerView = settings.slidesPerView; if (settings.slidesPerGroup) slidesPerGroup = settings.slidesPerGroup; } } } } function calculateSizes() { var containerWidth = container.offsetWidth; slideWidth = (containerWidth - ((slidesPerView - 1) * swiperOptions.spaceBetween)) / slidesPerView; // Apply sizes to slides slides.forEach(function(slide) { slide.style.flexShrink = '0'; slide.style.width = slideWidth + 'px'; slide.style.marginRight = swiperOptions.spaceBetween + 'px'; }); } function createClones() { // Clear any existing clones Array.from(wrapper.querySelectorAll('.swiper-clone')).forEach(function(clone) { clone.remove(); }); var originalSlides = slides.filter(function(slide) { return !slide.classList.contains('swiper-clone'); }); var numClones = slidesPerView * 2; // Clone enough slides // Add clones at beginning for (var i = Math.min(numClones, originalSlides.length) - 1; i >= 0; i--) { var clone = originalSlides[originalSlides.length - 1 - i].cloneNode(true); clone.classList.add('swiper-clone'); wrapper.insertBefore(clone, wrapper.firstChild); } // Add clones at end for (var i = 0; i < Math.min(numClones, originalSlides.length); i++) { var clone = originalSlides[i].cloneNode(true); clone.classList.add('swiper-clone'); wrapper.appendChild(clone); } // Update slides collection to include clones slides = Array.from(wrapper.children); // Re-bind link handlers for the cloned elements addLinkClickHandlers(); } function addTouchListeners() { wrapper.addEventListener('touchstart', onTouchStart, { passive: true }); wrapper.addEventListener('touchmove', onTouchMove, { passive: false }); wrapper.addEventListener('touchend', onTouchEnd); } function addMouseListeners() { wrapper.addEventListener('mousedown', onMouseDown); wrapper.addEventListener('mousemove', onMouseMove); wrapper.addEventListener('mouseup', onMouseUp); wrapper.addEventListener('mouseleave', onMouseUp); } function onTouchStart(e) { startX = e.touches[0].clientX; startY = e.touches[0].clientY; dragging = true; clickAllowed = true; // Reset click allowed state on touch start // Remove transition during dragging wrapper.style.transition = 'none'; } function onTouchMove(e) { if (!dragging) return; currentX = e.touches[0].clientX; var currentY = e.touches[0].clientY; // Calculate drag distance dragDistance = currentX - startX; var dragDistanceY = currentY - startY; // Check if we've moved enough to consider it a drag vs a click if (Math.abs(dragDistance) > moveThreshold) { clickAllowed = false; } // If horizontal movement is greater than vertical, prevent page scrolling if (Math.abs(dragDistance) > Math.abs(dragDistanceY)) { e.preventDefault(); } // Move the wrapper var translateX = -currentIndex * (slideWidth + swiperOptions.spaceBetween) + dragDistance; wrapper.style.transform = 'translate3d(' + translateX + 'px, 0, 0)'; } function onTouchEnd(e) { if (!dragging) return; dragging = false; // Re-add transition wrapper.style.transition = 'transform ' + swiperOptions.animationSpeed + 'ms ease'; // Determine if we should move to next/prev slide or stay on current if (Math.abs(dragDistance) > slideWidth / 3) { if (dragDistance > 0) { // Swiped right, go to prev slide goToSlide(currentIndex - slidesPerGroup); } else { // Swiped left, go to next slide goToSlide(currentIndex + slidesPerGroup); } } else { // Not enough movement, go back to current slide goToSlide(currentIndex); } // Reset drag state dragDistance = 0; } function onMouseDown(e) { // Only handle left mouse button if (e.button !== 0) return; e.preventDefault(); startX = e.clientX; startY = e.clientY; dragging = true; clickAllowed = true; // Reset click allowed state on mouse down // Remove transition during dragging wrapper.style.transition = 'none'; } function onMouseMove(e) { if (!dragging) return; currentX = e.clientX; dragDistance = currentX - startX; // Check if we've moved enough to consider it a drag vs a click if (Math.abs(dragDistance) > moveThreshold) { clickAllowed = false; } // Move the wrapper var translateX = -currentIndex * (slideWidth + swiperOptions.spaceBetween) + dragDistance; wrapper.style.transform = 'translate3d(' + translateX + 'px, 0, 0)'; } function onMouseUp(e) { if (!dragging) return; dragging = false; // Re-add transition wrapper.style.transition = 'transform ' + swiperOptions.animationSpeed + 'ms ease'; // Determine if we should move to next/prev slide or stay on current if (Math.abs(dragDistance) > slideWidth / 3) { if (dragDistance > 0) { // Moved right, go to prev slide goToSlide(currentIndex - slidesPerGroup); } else { // Moved left, go to next slide goToSlide(currentIndex + slidesPerGroup); } } else { // Not enough movement, go back to current slide goToSlide(currentIndex); } // Reset drag state dragDistance = 0; // Re-enable clicks after a short delay // This prevents accidental clicks right after a drag setTimeout(function() { clickAllowed = true; }, 100); } function onResize() { // Debounce resize events clearTimeout(resizeTimer); resizeTimer = setTimeout(function() { applyBreakpoints(); calculateSizes(); goToSlide(currentIndex, false); }, 200); } function goToSlide(index, animate) { animate = animate !== false; currentIndex = index; // Calculate position var translateX = -index * (slideWidth + swiperOptions.spaceBetween); // Apply transform wrapper.style.transition = animate ? 'transform ' + swiperOptions.animationSpeed + 'ms ease' : 'none'; wrapper.style.transform = 'translate3d(' + translateX + 'px, 0, 0)'; // Handle loop logic if (swiperOptions.loop) { clearTimeout(transitionTimer); transitionTimer = setTimeout(function() { var totalSlides = slides.length; var slidesBefore = slidesPerView; var slidesAfter = totalSlides - slidesPerView; // If we're at the beginning clones, jump to the end position if (index < slidesBefore) { goToSlide(totalSlides - slidesBefore - slidesBefore + index, false); } // If we're at the end clones, jump to the beginning position else if (index >= slidesAfter) { goToSlide(slidesBefore + (index - slidesAfter), false); } }, animate ? swiperOptions.animationSpeed : 0); } } // Initialize this swiper init(); // Return public methods if needed return { slideNext: function() { goToSlide(currentIndex + slidesPerGroup); }, slidePrev: function() { goToSlide(currentIndex - slidesPerGroup); } }; } } // Expose to window window.TouchSwiper = TouchSwiper; })();