@Saradh_Chandra Sure.
So first of all make add the alt text to your images.
Make sure the text is exactly same as of your variants.
Then on your product-media-gallery.liquid file add this entire code.
{% comment %}
Renders a product media gallery. Should be used with ‘media-gallery.js’
Also see ‘product-media-modal’
Accepts:
- product: {Object} Product liquid object
- variant_images: {Array} Product images associated with a variant
- limit: {Number} (optional) When passed, limits the number of media items to render
Usage:
{% render ‘product-media-gallery’ %}
{% endcomment %}
{%- liquid
if section.settings.hide_variants and variant_images.size == product.media.size
assign single_media_visible = true
endif
if limit == 1
assign single_media_visible = true
endif
assign media_count = product.media.size
if section.settings.hide_variants and media_count > 1 and variant_images.size > 0
assign media_count = media_count | minus: variant_images.size | plus: 1
endif
if media_count == 1 or single_media_visible
assign single_media_visible_mobile = true
endif
if media_count == 0 or single_media_visible_mobile or section.settings.mobile_thumbnails == ‘show’ or section.settings.mobile_thumbnails == ‘columns’ and media_count < 3
assign hide_mobile_slider = true
endif
if section.settings.media_size == ‘large’
assign media_width = 0.65
elsif section.settings.media_size == ‘medium’
assign media_width = 0.55
elsif section.settings.media_size == ‘small’
assign media_width = 0.45
endif
-%}
<media-gallery
id=“MediaGallery-{{ section.id }}”
role=“region”
{% if section.settings.enable_sticky_info %}
class=“product__column-sticky”
{% endif %}
aria-label=“{{ ‘products.product.media.gallery_viewer’ | t }}”
data-desktop-layout=“{{ section.settings.gallery_layout }}”
{{ 'accessibility.skip_to_product_info' | t }}
{% render 'icon-caret' %}
1
/
{{ 'general.slider.of' | t }}
{{ media_count }}
{% render 'icon-caret' %}
{%- if first_3d_model -%}
{% render 'icon-3d-model' %}
{{ 'products.product.xr_button' | t }}
{%- endif -%}
{%- liquid
assign is_not_limited_to_single_item = false
if limit == null or limit > 1
assign is_not_limited_to_single_item = true
endif
-%}
{%- if is_not_limited_to_single_item
and media_count > 1
and section.settings.gallery_layout contains 'thumbnail'
or section.settings.mobile_thumbnails == 'show'
-%}
{% render 'icon-caret' %}
{%- capture sizes -%}
(min-width: {{ settings.page_width }}px) calc(({{ settings.page_width | minus: 100 | times: media_width | round }} - 4rem) / 4),
(min-width: 990px) calc(({{ media_width | times: 100 }}vw - 4rem) / 4),
(min-width: 750px) calc((100vw - 15rem) / 8),
calc((100vw - 8rem) / 3)
{%- endcapture -%}
{%- if featured_media != null -%}
{%- liquid
capture media_index
if featured_media.media_type == ‘model’
increment model_index
elsif featured_media.media_type == ‘video’ or featured_media.media_type == ‘external_video’
increment video_index
elsif featured_media.media_type == ‘image’
increment image_index
endif
endcapture
assign media_index = media_index | plus: 1
-%}
-
{%- capture thumbnail_id -%}
Thumbnail-{{ section.id }}-0
{%- endcapture -%}
{{
featured_media.preview_image
| image_url: width: 416
| image_tag:
loading: 'lazy',
sizes: sizes,
widths: '54, 74, 104, 162, 208, 324, 416',
id: thumbnail_id,
alt: featured_media.alt
| escape
}}
{%- endif -%}
{%- for media in product.media -%}
{%- unless media.id == product.selected_or_first_available_variant.featured_media.id -%}
{%- liquid
capture media_index
if media.media_type == 'model'
increment model_index
elsif media.media_type == 'video' or media.media_type == 'external_video'
increment video_index
elsif media.media_type == 'image'
increment image_index
endif
endcapture
assign media_index = media_index | plus: 1
-%}
-
{%- if media.media_type == 'model' -%}
{%- render 'icon-3d-model' -%}
{%- elsif media.media_type == 'video' or media.media_type == 'external_video' -%}
{%- render 'icon-play' -%}
{%- endif -%}
{%- capture thumbnail_id -%}
Thumbnail-{{ section.id }}-{{ forloop.index }}
{%- endcapture -%}
{{
media.preview_image
| image_url: width: 416
| image_tag:
loading: 'lazy',
sizes: sizes,
widths: '54, 74, 104, 162, 208, 324, 416',
id: thumbnail_id,
alt: media.alt
| escape
}}
{%- endunless -%}
{%- endfor -%}
{% render 'icon-caret' %}
{%- endif -%}
And then in product-info.js add this code
if (!customElements.get(‘product-info’)) {
customElements.define(
‘product-info’,
class ProductInfo extends HTMLElement {
quantityInput = undefined;
quantityForm = undefined;
onVariantChangeUnsubscriber = undefined;
cartUpdateUnsubscriber = undefined;
abortController = undefined;
pendingRequestUrl = null;
preProcessHtmlCallbacks = ;
postProcessHtmlCallbacks = ;
constructor() {
super();
this.quantityInput = this.querySelector(‘.quantity__input’);
}
connectedCallback() {
this.initializeProductSwapUtility();
this.onVariantChangeUnsubscriber = subscribe(
PUB_SUB_EVENTS.optionValueSelectionChange,
this.handleOptionValueChange.bind(this)
);
this.initQuantityHandlers();
this.dispatchEvent(new CustomEvent(‘product-info:loaded’, { bubbles: true }));
}
addPreProcessCallback(callback) {
this.preProcessHtmlCallbacks.push(callback);
}
initQuantityHandlers() {
if (!this.quantityInput) return;
this.quantityForm = this.querySelector(‘.product-form__quantity’);
if (!this.quantityForm) return;
this.setQuantityBoundries();
if (!this.dataset.originalSection) {
this.cartUpdateUnsubscriber = subscribe(PUB_SUB_EVENTS.cartUpdate, this.fetchQuantityRules.bind(this));
}
}
disconnectedCallback() {
this.onVariantChangeUnsubscriber();
this.cartUpdateUnsubscriber?.();
}
initializeProductSwapUtility() {
this.preProcessHtmlCallbacks.push((html) =>
html.querySelectorAll(‘.scroll-trigger’).forEach((element) => element.classList.add(‘scroll-trigger–cancel’))
);
this.postProcessHtmlCallbacks.push((newNode) => {
window?.Shopify?.PaymentButton?.init();
window?.ProductModel?.loadShopifyXR();
});
}
handleOptionValueChange({ data: { event, target, selectedOptionValues } }) {
if (!this.contains(event.target)) return;
this.resetProductFormState();
const productUrl = target.dataset.productUrl || this.pendingRequestUrl || this.dataset.url;
this.pendingRequestUrl = productUrl;
const shouldSwapProduct = this.dataset.url !== productUrl;
const shouldFetchFullPage = this.dataset.updateUrl === ‘true’ && shouldSwapProduct;
this.renderProductInfo({
requestUrl: this.buildRequestUrlWithParams(productUrl, selectedOptionValues, shouldFetchFullPage),
targetId: target.id,
callback: shouldSwapProduct
? this.handleSwapProduct(productUrl, shouldFetchFullPage)
: this.handleUpdateProductInfo(productUrl),
});
}
resetProductFormState() {
const productForm = this.productForm;
productForm?.toggleSubmitButton(true);
productForm?.handleErrorMessage();
}
handleSwapProduct(productUrl, updateFullPage) {
return (html) => {
this.productModal?.remove();
const selector = updateFullPage ? “product-info[id^=‘MainProduct’]” : ‘product-info’;
const variant = this.getSelectedVariant(html.querySelector(selector));
this.updateURL(productUrl, variant?.id);
if (updateFullPage) {
document.querySelector(‘head title’).innerHTML = html.querySelector(‘head title’).innerHTML;
HTMLUpdateUtility.viewTransition(
document.querySelector(‘main’),
html.querySelector(‘main’),
this.preProcessHtmlCallbacks,
this.postProcessHtmlCallbacks
);
} else {
HTMLUpdateUtility.viewTransition(
this,
html.querySelector(‘product-info’),
this.preProcessHtmlCallbacks,
this.postProcessHtmlCallbacks
);
}
};
}
renderProductInfo({ requestUrl, targetId, callback }) {
this.abortController?.abort();
this.abortController = new AbortController();
fetch(requestUrl, { signal: this.abortController.signal })
.then((response) => response.text())
.then((responseText) => {
this.pendingRequestUrl = null;
const html = new DOMParser().parseFromString(responseText, ‘text/html’);
callback(html);
})
.then(() => {
// set focus to last clicked option value
document.querySelector(#${targetId})?.focus();
})
.catch((error) => {
if (error.name === ‘AbortError’) {
console.log(‘Fetch aborted by user’);
} else {
console.error(error);
}
});
}
getSelectedVariant(productInfoNode) {
const selectedVariant = productInfoNode.querySelector(‘variant-selects [data-selected-variant]’)?.innerHTML;
return !!selectedVariant ? JSON.parse(selectedVariant) : null;
}
buildRequestUrlWithParams(url, optionValues, shouldFetchFullPage = false) {
const params = ;
!shouldFetchFullPage && params.push(section_id=${this.sectionId});
if (optionValues.length) {
params.push(option_values=${optionValues.join(',')});
}
return ${url}?${params.join('&')};
}
updateOptionValues(html) {
const variantSelects = html.querySelector(‘variant-selects’);
if (variantSelects) {
HTMLUpdateUtility.viewTransition(this.variantSelectors, variantSelects, this.preProcessHtmlCallbacks);
}
}
handleUpdateProductInfo(productUrl) {
return (html) => {
const variant = this.getSelectedVariant(html);
this.pickupAvailability?.update(variant);
this.updateOptionValues(html);
this.updateURL(productUrl, variant?.id);
this.updateVariantInputs(variant?.id);
if (!variant) {
this.setUnavailable();
return;
}
this.updateMedia(html, variant?.featured_media?.id);
this.filterVariantImages(variant);
const updateSourceFromDestination = (id, shouldHide = (source) => false) => {
const source = html.getElementById(${id}-${this.sectionId});
const destination = this.querySelector(#${id}-${this.dataset.section});
if (source && destination) {
destination.innerHTML = source.innerHTML;
destination.classList.toggle(‘hidden’, shouldHide(source));
}
};
updateSourceFromDestination(‘price’);
updateSourceFromDestination(‘Sku’, ({ classList }) => classList.contains(‘hidden’));
updateSourceFromDestination(‘Inventory’, ({ innerText }) => innerText === ‘’);
updateSourceFromDestination(‘Volume’);
updateSourceFromDestination(‘Price-Per-Item’, ({ classList }) => classList.contains(‘hidden’));
this.updateQuantityRules(this.sectionId, html);
this.querySelector(#Quantity-Rules-${this.dataset.section})?.classList.remove(‘hidden’);
this.querySelector(#Volume-Note-${this.dataset.section})?.classList.remove(‘hidden’);
this.productForm?.toggleSubmitButton(
html.getElementById(ProductSubmitButton-${this.sectionId})?.hasAttribute(‘disabled’) ?? true,
window.variantStrings.soldOut
);
publish(PUB_SUB_EVENTS.variantChange, {
data: {
sectionId: this.sectionId,
html,
variant,
},
});
};
}
filterVariantImages(varObj){
if(varObj.featured_image && varObj.featured_image.alt){
document.querySelectorAll(‘[thumbnail-alt]’).forEach(img => img.style.display = ‘none’);
const currentImageAlt = varObj.featured_image.alt;
const thumbnailSelector= [thumbnail-alt= '${currentImageAlt}'];
document.querySelectorAll(thumbnailSelector).forEach( (img) => {
img.style.display = ‘block’;
})
} else{
document.querySelectorAll(‘[thumbnail-alt]’).forEach(img => img.style.display = ‘block’);
}
}
updateVariantInputs(variantId) {
this.querySelectorAll(
#product-form-${this.dataset.section}, #product-form-installment-${this.dataset.section}
).forEach((productForm) => {
const input = productForm.querySelector(‘input[name=“id”]’);
input.value = variantId ?? ‘’;
input.dispatchEvent(new Event(‘change’, { bubbles: true }));
});
}
updateURL(url, variantId) {
this.querySelector(‘share-button’)?.updateUrl(
${window.shopUrl}${url}${variantId ? ?variant=${variantId} : ''}
);
if (this.dataset.updateUrl === ‘false’) return;
window.history.replaceState({}, ‘’, ${url}${variantId ? ?variant=${variantId} : ''});
}
setUnavailable() {
this.productForm?.toggleSubmitButton(true, window.variantStrings.unavailable);
const selectors = [‘price’, ‘Inventory’, ‘Sku’, ‘Price-Per-Item’, ‘Volume-Note’, ‘Volume’, ‘Quantity-Rules’]
.map((id) => #${id}-${this.dataset.section})
.join(', ');
document.querySelectorAll(selectors).forEach(({ classList }) => classList.add(‘hidden’));
}
updateMedia(html, variantFeaturedMediaId) {
if (!variantFeaturedMediaId) return;
const mediaGallerySource = this.querySelector(‘media-gallery ul’);
const mediaGalleryDestination = html.querySelector(media-gallery ul);
const refreshSourceData = () => {
if (this.hasAttribute(‘data-zoom-on-hover’)) enableZoomOnHover(2);
const mediaGallerySourceItems = Array.from(mediaGallerySource.querySelectorAll(‘li[data-media-id]’));
const sourceSet = new Set(mediaGallerySourceItems.map((item) => item.dataset.mediaId));
const sourceMap = new Map(
mediaGallerySourceItems.map((item, index) => [item.dataset.mediaId, { item, index }])
);
return [mediaGallerySourceItems, sourceSet, sourceMap];
};
if (mediaGallerySource && mediaGalleryDestination) {
let [mediaGallerySourceItems, sourceSet, sourceMap] = refreshSourceData();
const mediaGalleryDestinationItems = Array.from(
mediaGalleryDestination.querySelectorAll(‘li[data-media-id]’)
);
const destinationSet = new Set(mediaGalleryDestinationItems.map(({ dataset }) => dataset.mediaId));
let shouldRefresh = false;
// add items from new data not present in DOM
for (let i = mediaGalleryDestinationItems.length - 1; i >= 0; i–) {
if (!sourceSet.has(mediaGalleryDestinationItems[i].dataset.mediaId)) {
mediaGallerySource.prepend(mediaGalleryDestinationItems[i]);
shouldRefresh = true;
}
}
// remove items from DOM not present in new data
for (let i = 0; i < mediaGallerySourceItems.length; i++) {
if (!destinationSet.has(mediaGallerySourceItems[i].dataset.mediaId)) {
mediaGallerySourceItems[i].remove();
shouldRefresh = true;
}
}
// refresh
if (shouldRefresh) [mediaGallerySourceItems, sourceSet, sourceMap] = refreshSourceData();
// if media galleries don’t match, sort to match new data order
mediaGalleryDestinationItems.forEach((destinationItem, destinationIndex) => {
const sourceData = sourceMap.get(destinationItem.dataset.mediaId);
if (sourceData && sourceData.index !== destinationIndex) {
mediaGallerySource.insertBefore(
sourceData.item,
mediaGallerySource.querySelector(li:nth-of-type(${destinationIndex + 1}))
);
// refresh source now that it has been modified
[mediaGallerySourceItems, sourceSet, sourceMap] = refreshSourceData();
}
});
}
// set featured media as active in the media gallery
this.querySelector(media-gallery)?.setActiveMedia?.(
${this.dataset.section}-${variantFeaturedMediaId},
true
);
// update media modal
const modalContent = this.productModal?.querySelector(.product-media-modal__content);
const newModalContent = html.querySelector(product-modal .product-media-modal__content);
if (modalContent && newModalContent) modalContent.innerHTML = newModalContent.innerHTML;
}
setQuantityBoundries() {
const data = {
cartQuantity: this.quantityInput.dataset.cartQuantity ? parseInt(this.quantityInput.dataset.cartQuantity) : 0,
min: this.quantityInput.dataset.min ? parseInt(this.quantityInput.dataset.min) : 1,
max: this.quantityInput.dataset.max ? parseInt(this.quantityInput.dataset.max) : null,
step: this.quantityInput.step ? parseInt(this.quantityInput.step) : 1,
};
let min = data.min;
const max = data.max === null ? data.max : data.max - data.cartQuantity;
if (max !== null) min = Math.min(min, max);
if (data.cartQuantity >= data.min) min = Math.min(min, data.step);
this.quantityInput.min = min;
if (max) {
this.quantityInput.max = max;
} else {
this.quantityInput.removeAttribute(‘max’);
}
this.quantityInput.value = min;
publish(PUB_SUB_EVENTS.quantityUpdate, undefined);
}
fetchQuantityRules() {
const currentVariantId = this.productForm?.variantIdInput?.value;
if (!currentVariantId) return;
this.querySelector(‘.quantity__rules-cart .loading__spinner’).classList.remove(‘hidden’);
fetch(${this.dataset.url}?variant=${currentVariantId}§ion_id=${this.dataset.section})
.then((response) => response.text())
.then((responseText) => {
const html = new DOMParser().parseFromString(responseText, ‘text/html’);
this.updateQuantityRules(this.dataset.section, html);
})
.catch((e) => console.error(e))
.finally(() => this.querySelector(‘.quantity__rules-cart .loading__spinner’).classList.add(‘hidden’));
}
updateQuantityRules(sectionId, html) {
if (!this.quantityInput) return;
this.setQuantityBoundries();
const quantityFormUpdated = html.getElementById(Quantity-Form-${sectionId});
const selectors = [‘.quantity__input’, ‘.quantity__rules’, ‘.quantity__label’];
for (let selector of selectors) {
const current = this.quantityForm.querySelector(selector);
const updated = quantityFormUpdated.querySelector(selector);
if (!current || !updated) continue;
if (selector === ‘.quantity__input’) {
const attributes = [‘data-cart-quantity’, ‘data-min’, ‘data-max’, ‘step’];
for (let attribute of attributes) {
const valueUpdated = updated.getAttribute(attribute);
if (valueUpdated !== null) {
current.setAttribute(attribute, valueUpdated);
} else {
current.removeAttribute(attribute);
}
}
} else {
current.innerHTML = updated.innerHTML;
}
}
}
get productForm() {
return this.querySelector(product-form);
}
get productModal() {
return document.querySelector(#ProductModal-${this.dataset.section});
}
get pickupAvailability() {
return this.querySelector(pickup-availability);
}
get variantSelectors() {
return this.querySelector(‘variant-selects’);
}
get relatedProducts() {
const relatedProductsSectionId = SectionId.getIdForSection(
SectionId.parseId(this.sectionId),
‘related-products’
);
return document.querySelector(product-recommendations[data-section-id^="${relatedProductsSectionId}"]);
}
get quickOrderList() {
const quickOrderListSectionId = SectionId.getIdForSection(
SectionId.parseId(this.sectionId),
‘quick_order_list’
);
return document.querySelector(quick-order-list[data-id^="${quickOrderListSectionId}"]);
}
get sectionId() {
return this.dataset.originalSection || this.dataset.section;
}
}
);
}
i believe this should do it. Let me know if you encounter any problem. Dont forget to like and mark it if it helps.
Email me if you need any further help related to code, theme customizations for your business.
Buy Me Some Coffee if you feel i was helpful and i deserve it. Does act as a motivation.
Thanks anyways