Skip to content

Instantly share code, notes, and snippets.

@TanvirHasan19
Last active December 10, 2025 11:35
Show Gist options
  • Select an option

  • Save TanvirHasan19/b4bb81a7b33b865821fa52591d8cf587 to your computer and use it in GitHub Desktop.

Select an option

Save TanvirHasan19/b4bb81a7b33b865821fa52591d8cf587 to your computer and use it in GitHub Desktop.
Vertical thumbnails with scroll arrows
/**
* 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