Last active
December 10, 2025 11:35
-
-
Save TanvirHasan19/b4bb81a7b33b865821fa52591d8cf587 to your computer and use it in GitHub Desktop.
Vertical thumbnails with scroll arrows
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Oz Robotics product gallery | |
| * Vertical thumbnails with scroll arrows | |
| */ | |
| add_action( 'wp_head', 'oz_gallery_thumbs_styles' ); | |
| function oz_gallery_thumbs_styles() { | |
| if ( ! function_exists( 'is_product' ) || ! is_product() ) { | |
| return; | |
| } | |
| ?> | |
| <style> | |
| @media (min-width: 1024px) { | |
| /* Reset default WooCommerce gallery styles */ | |
| .single-product div.product .woocommerce-product-gallery .flex-control-thumbs { | |
| display: block !important; | |
| float: none !important; | |
| width: auto !important; | |
| } | |
| .single-product div.product .woocommerce-product-gallery .flex-control-thumbs li { | |
| float: none !important; | |
| width: auto !important; | |
| clear: none !important; | |
| } | |
| /* Main gallery container */ | |
| .single-product div.product .woocommerce-product-gallery { | |
| display: flex !important; | |
| align-items: flex-start; | |
| gap: 24px; | |
| float: none; | |
| width: 100%; | |
| position: relative; | |
| } | |
| /* Main image on the right - used to calculate thumbnail height */ | |
| .single-product div.product | |
| .woocommerce-product-gallery .flex-viewport { | |
| order: 2; | |
| flex: 1 1 auto; | |
| width: auto; | |
| height: auto !important; | |
| min-width: 0; | |
| } | |
| /* Column that holds arrows + thumbnails */ | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumbs-wrapper { | |
| order: 1; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| flex: 0 0 150px; | |
| gap: 12px; | |
| position: relative; | |
| min-width: 150px; | |
| align-self: stretch; | |
| height: 100%; | |
| } | |
| /* When there are 6 or fewer thumbnails, match main image height */ | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumbs-wrapper:not(.has-many-thumbs) { | |
| align-self: stretch; | |
| } | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumbs-wrapper:not(.has-many-thumbs) .wc-oz-thumbs-list { | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: flex-start; | |
| height: 100%; | |
| } | |
| /* Scrollable list of thumbnails */ | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumbs-list { | |
| list-style: none !important; | |
| margin: 0 !important; | |
| padding: 0 !important; | |
| flex: 1 1 auto; | |
| min-height: 0; | |
| overflow-y: auto; | |
| overflow-x: hidden; | |
| scroll-behavior: smooth; | |
| width: 100%; | |
| display: flex; | |
| flex-direction: column; | |
| /* Hide scrollbar - only show arrows */ | |
| scrollbar-width: none; /* Firefox */ | |
| -ms-overflow-style: none; /* IE and Edge */ | |
| } | |
| /* Hide scrollbar for Webkit browsers (Chrome, Safari, Edge) */ | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumbs-list::-webkit-scrollbar { | |
| display: none; | |
| width: 0; | |
| height: 0; | |
| } | |
| /* When there are more than 6 thumbnails, make it scrollable and show only 6 */ | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumbs-wrapper.has-many-thumbs .wc-oz-thumbs-list { | |
| flex: 0 1 auto; | |
| min-height: 0; | |
| overflow-y: auto; | |
| /* Will be set dynamically by JavaScript to show exactly 6 thumbnails */ | |
| } | |
| /* Limit visible thumbnails using CSS - show first 6 items */ | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumbs-wrapper.has-many-thumbs .wc-oz-thumbs-list li:nth-child(n+7) { | |
| /* Additional items beyond 6 are hidden by scroll */ | |
| } | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumbs-list li { | |
| width: 100% !important; | |
| margin: 0 0 12px 0 !important; | |
| padding: 0 !important; | |
| display: block !important; | |
| float: none !important; | |
| clear: none !important; | |
| list-style: none !important; | |
| } | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumbs-list li:last-child { | |
| margin-bottom: 0 !important; | |
| } | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumbs-list li img { | |
| display: block !important; | |
| width: 100% !important; | |
| max-width: 100% !important; | |
| height: auto !important; | |
| border: 1px solid #e0e0e0; | |
| border-radius: 4px; | |
| transition: all 0.2s ease; | |
| cursor: pointer; | |
| background: #fff; | |
| opacity: 0.7; | |
| margin: 0 !important; | |
| } | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumbs-list li:hover img { | |
| border-color: #999; | |
| opacity: 1; | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |
| } | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumbs-list li.flex-active-slide img, | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumbs-list li img.flex-active { | |
| border-color: #333; | |
| border-width: 2px; | |
| opacity: 1; | |
| box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); | |
| } | |
| /* Arrow buttons - positioned at top and bottom */ | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumb-arrow { | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| width: 40px; | |
| height: 40px; | |
| border-radius: 4px; | |
| border: 1px solid #ddd; | |
| padding: 0; | |
| cursor: pointer; | |
| font-size: 18px; | |
| line-height: 1; | |
| background: #ffffff; | |
| color: #333; | |
| transition: all 0.2s ease; | |
| flex-shrink: 0; | |
| box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); | |
| z-index: 10; | |
| order: 0; | |
| } | |
| /* Up arrow at top */ | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumb-arrow-up { | |
| order: 1; | |
| } | |
| /* Thumbnails list in middle */ | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumbs-list { | |
| order: 2; | |
| } | |
| /* Down arrow at bottom */ | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumb-arrow-down { | |
| order: 3; | |
| } | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumb-arrow:hover:not(:disabled) { | |
| background: #f5f5f5; | |
| border-color: #999; | |
| color: #000; | |
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15); | |
| transform: translateY(-1px); | |
| } | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumb-arrow:active:not(:disabled) { | |
| background: #e8e8e8; | |
| transform: translateY(0) scale(0.98); | |
| } | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumb-arrow:disabled { | |
| opacity: 0.4; | |
| cursor: not-allowed; | |
| background: #f9f9f9; | |
| } | |
| .single-product div.product | |
| .woocommerce-product-gallery__wrapper, | |
| .single-product div.product | |
| .woocommerce-product-gallery__image img { | |
| width: 100%; | |
| height: auto; | |
| object-fit: contain; | |
| } | |
| /* Hide arrows when not needed (6 or fewer thumbnails) */ | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumbs-wrapper:not(.has-many-thumbs) .wc-oz-thumb-arrow { | |
| display: none !important; | |
| } | |
| /* Ensure proper spacing when arrows are visible */ | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumbs-wrapper.has-many-thumbs { | |
| gap: 12px; | |
| } | |
| /* Match thumbnail wrapper height to main image when 6 or fewer thumbnails */ | |
| .single-product div.product | |
| .woocommerce-product-gallery .wc-oz-thumbs-wrapper:not(.has-many-thumbs) { | |
| align-self: stretch; | |
| } | |
| } | |
| </style> | |
| <?php | |
| } | |
| add_action( 'wp_footer', 'oz_gallery_thumbs_script' ); | |
| function oz_gallery_thumbs_script() { | |
| if ( ! function_exists( 'is_product' ) || ! is_product() ) { | |
| return; | |
| } | |
| ?> | |
| <script> | |
| (function () { | |
| function ozInitThumbArrows() { | |
| var gallery = document.querySelector('.single-product div.product .woocommerce-product-gallery'); | |
| if (!gallery) { | |
| return; | |
| } | |
| // Avoid running twice | |
| if (gallery.querySelector('.wc-oz-thumbs-wrapper')) { | |
| return; | |
| } | |
| var thumbs = gallery.querySelector('.flex-control-nav.flex-control-thumbs'); | |
| if (!thumbs) { | |
| return; | |
| } | |
| var items = thumbs.querySelectorAll('li'); | |
| var hasManyThumbs = items && items.length > 6; | |
| var wrapper = document.createElement('div'); | |
| if (hasManyThumbs) { | |
| wrapper.className = 'wc-oz-thumbs-wrapper has-many-thumbs'; | |
| } else { | |
| wrapper.className = 'wc-oz-thumbs-wrapper'; | |
| } | |
| // Insert wrapper before thumbs and move thumbs inside | |
| thumbs.parentNode.insertBefore(wrapper, thumbs); | |
| wrapper.appendChild(thumbs); | |
| thumbs.classList.add('wc-oz-thumbs-list'); | |
| var upBtn = document.createElement('button'); | |
| upBtn.type = 'button'; | |
| upBtn.className = 'wc-oz-thumb-arrow wc-oz-thumb-arrow-up'; | |
| upBtn.innerHTML = '▲'; | |
| upBtn.setAttribute('aria-label', 'Scroll thumbnails up'); | |
| var downBtn = document.createElement('button'); | |
| downBtn.type = 'button'; | |
| downBtn.className = 'wc-oz-thumb-arrow wc-oz-thumb-arrow-down'; | |
| downBtn.innerHTML = '▼'; | |
| downBtn.setAttribute('aria-label', 'Scroll thumbnails down'); | |
| // Only add arrows if more than 6 thumbnails | |
| if (hasManyThumbs) { | |
| wrapper.insertBefore(upBtn, thumbs); | |
| wrapper.appendChild(downBtn); | |
| } | |
| var scrollAmount = 180; | |
| // Function to sync thumbnail wrapper height with main image | |
| function syncThumbnailHeight() { | |
| if (!hasManyThumbs) { | |
| var viewport = gallery.querySelector('.flex-viewport'); | |
| var mainImage = viewport ? viewport.querySelector('img') : null; | |
| if (mainImage && wrapper) { | |
| // Wait for image to load | |
| if (mainImage.complete) { | |
| var mainImageHeight = mainImage.offsetHeight || mainImage.clientHeight; | |
| if (mainImageHeight > 0) { | |
| wrapper.style.height = mainImageHeight + 'px'; | |
| } | |
| } else { | |
| mainImage.addEventListener('load', function() { | |
| var imgHeight = this.offsetHeight || this.clientHeight; | |
| if (imgHeight > 0) { | |
| wrapper.style.height = imgHeight + 'px'; | |
| } | |
| }); | |
| } | |
| } | |
| } | |
| } | |
| // Function to set max-height for 6 thumbnails when there are more than 6 | |
| function setThumbnailListHeight() { | |
| if (hasManyThumbs && items.length > 6) { | |
| // Measure the actual height of the first 6 items | |
| var firstSixItems = []; | |
| for (var i = 0; i < Math.min(6, items.length); i++) { | |
| firstSixItems.push(items[i]); | |
| } | |
| // Temporarily remove max-height to get accurate measurements | |
| var originalMaxHeight = thumbs.style.maxHeight; | |
| thumbs.style.maxHeight = 'none'; | |
| // Calculate total height of first 6 items | |
| var totalHeight = 0; | |
| var allLoaded = true; | |
| firstSixItems.forEach(function(item, index) { | |
| var itemHeight = item.offsetHeight; | |
| if (itemHeight > 0) { | |
| totalHeight += itemHeight; | |
| } else { | |
| allLoaded = false; | |
| // Estimate based on image | |
| var img = item.querySelector('img'); | |
| if (img && img.complete && img.naturalHeight > 0) { | |
| totalHeight += img.offsetHeight || 150; | |
| } else { | |
| totalHeight += 150; // fallback (increased to 150px) | |
| } | |
| } | |
| }); | |
| // Restore max-height with calculated value | |
| if (totalHeight > 0) { | |
| thumbs.style.maxHeight = totalHeight + 'px'; | |
| } else { | |
| // Fallback: 6 thumbnails * 150px + 5 gaps * 12px = 960px | |
| thumbs.style.maxHeight = '960px'; | |
| } | |
| // If not all images loaded, recalculate after a delay | |
| if (!allLoaded) { | |
| setTimeout(function() { | |
| setThumbnailListHeight(); | |
| }, 300); | |
| } | |
| // Also recalculate when images load | |
| firstSixItems.forEach(function(item) { | |
| var img = item.querySelector('img'); | |
| if (img && !img.complete) { | |
| var loadHandler = function() { | |
| setThumbnailListHeight(); | |
| img.removeEventListener('load', loadHandler); | |
| }; | |
| img.addEventListener('load', loadHandler); | |
| } | |
| }); | |
| } | |
| } | |
| function updateArrowStates() { | |
| if (!upBtn || !downBtn) return; | |
| var scrollTop = thumbs.scrollTop; | |
| var scrollHeight = thumbs.scrollHeight; | |
| var clientHeight = thumbs.clientHeight; | |
| var maxScroll = scrollHeight - clientHeight; | |
| // Update up arrow - disable at top | |
| if (scrollTop <= 5) { | |
| upBtn.disabled = true; | |
| } else { | |
| upBtn.disabled = false; | |
| } | |
| // Update down arrow - disable at bottom | |
| if (maxScroll <= 5 || scrollTop >= maxScroll - 5) { | |
| downBtn.disabled = true; | |
| } else { | |
| downBtn.disabled = false; | |
| } | |
| } | |
| if (hasManyThumbs) { | |
| upBtn.addEventListener('click', function (e) { | |
| e.preventDefault(); | |
| if (!upBtn.disabled) { | |
| thumbs.scrollBy({ top: -scrollAmount, behavior: 'smooth' }); | |
| // Update state after scroll | |
| setTimeout(updateArrowStates, 300); | |
| } | |
| }); | |
| downBtn.addEventListener('click', function (e) { | |
| e.preventDefault(); | |
| if (!downBtn.disabled) { | |
| thumbs.scrollBy({ top: scrollAmount, behavior: 'smooth' }); | |
| // Update state after scroll | |
| setTimeout(updateArrowStates, 300); | |
| } | |
| }); | |
| // Update arrow states on scroll | |
| thumbs.addEventListener('scroll', function() { | |
| updateArrowStates(); | |
| }); | |
| } | |
| // Initial state check - wait a bit for layout to settle | |
| if (hasManyThumbs) { | |
| setTimeout(function() { | |
| setThumbnailListHeight(); | |
| updateArrowStates(); | |
| }, 200); | |
| } | |
| // Sync height for 6 or fewer thumbnails | |
| if (!hasManyThumbs) { | |
| setTimeout(function() { | |
| syncThumbnailHeight(); | |
| }, 200); | |
| } | |
| // Update on window resize | |
| var resizeTimer; | |
| window.addEventListener('resize', function() { | |
| clearTimeout(resizeTimer); | |
| resizeTimer = setTimeout(function() { | |
| if (hasManyThumbs) { | |
| setThumbnailListHeight(); | |
| updateArrowStates(); | |
| } else { | |
| syncThumbnailHeight(); | |
| } | |
| }, 150); | |
| }); | |
| // Update after images load | |
| var images = thumbs.querySelectorAll('img'); | |
| var imagesLoaded = 0; | |
| var totalImages = images.length; | |
| if (totalImages > 0) { | |
| images.forEach(function(img) { | |
| if (img.complete) { | |
| imagesLoaded++; | |
| } else { | |
| img.addEventListener('load', function() { | |
| imagesLoaded++; | |
| if (imagesLoaded === totalImages) { | |
| setTimeout(updateArrowStates, 200); | |
| } | |
| }); | |
| img.addEventListener('error', function() { | |
| imagesLoaded++; | |
| if (imagesLoaded === totalImages) { | |
| setTimeout(updateArrowStates, 200); | |
| } | |
| }); | |
| } | |
| }); | |
| if (imagesLoaded === totalImages) { | |
| if (hasManyThumbs) { | |
| setTimeout(function() { | |
| setThumbnailListHeight(); | |
| updateArrowStates(); | |
| }, 200); | |
| } else { | |
| setTimeout(syncThumbnailHeight, 200); | |
| } | |
| } | |
| } | |
| // Also update when gallery slider changes | |
| var viewport = gallery.querySelector('.flex-viewport'); | |
| if (viewport) { | |
| var observer = new MutationObserver(function() { | |
| setTimeout(updateArrowStates, 100); | |
| }); | |
| observer.observe(viewport, { childList: true, subtree: true }); | |
| } | |
| } | |
| // Initialize on page load | |
| function initGallery() { | |
| setTimeout(ozInitThumbArrows, 100); | |
| } | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', initGallery); | |
| } else { | |
| initGallery(); | |
| } | |
| window.addEventListener('load', function() { | |
| setTimeout(ozInitThumbArrows, 300); | |
| }); | |
| // Listen for WooCommerce gallery initialization | |
| document.body.addEventListener('wc_product_gallery_init', function() { | |
| setTimeout(ozInitThumbArrows, 150); | |
| }); | |
| // Also try after jQuery ready (WooCommerce uses jQuery) | |
| if (typeof jQuery !== 'undefined') { | |
| jQuery(document).ready(function($) { | |
| setTimeout(ozInitThumbArrows, 200); | |
| }); | |
| } | |
| })(); | |
| </script> | |
| <?php | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment