Personalized checkout and custom promotions with Shopify Scripts
Hi, I currently have a custom 3D viewer that works with variant switching keeping the 3D viewer at the top of the screen on desktop however, on mobile or thinner screens the image appears first. I have linked my code from product-media-gallery.liquid below and a screenshot.
<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 }}"
>
<div id="GalleryStatus-{{ section.id }}" class="visually-hidden" role="status"></div>
<slider-component id="GalleryViewer-{{ section.id }}" class="slider-mobile-gutter">
<a class="skip-to-content-link button visually-hidden quick-add-hidden" href="#ProductInfo-{{ section.id }}">
{{ 'accessibility.skip_to_product_info' | t }}
</a>
<ul
id="Slider-Gallery-{{ section.id }}"
class="product__media-list contains-media grid grid--peek list-unstyled slider slider--mobile"
role="list"
>
<!-- 3D Viewer integration start -->
{% assign first_variant = product.variants.first %}
{% if first_variant.metafields.custom.viewer %}
<li class="product__media-item grid__item slider__slide" data-media-id="{{ section.id }}-3d-viewer" style="order: -10;">
<div class="product-3d-viewer-container">
<iframe
id="product-viewer-iframe-{{ section.id }}"
src="{{ first_variant.metafields.custom.viewer }}"
class="product-3d-viewer-iframe"
allowfullscreen
loading="lazy"
></iframe>
</div>
</li>
{% endif %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const sectionId = '{{ section.id }}';
const iframe = document.querySelector(`#product-viewer-iframe-${sectionId}`);
const variantInput = document.querySelector(`#product-form-${sectionId} [name="id"]`);
if (!iframe || !variantInput) return;
const variantViewers = {};
{% for variant in product.variants %}
{% if variant.metafields.custom.viewer %}
variantViewers['{{ variant.id }}'] = '{{ variant.metafields.custom.viewer }}';
{% endif %}
{% endfor %}
function updateViewer(variantId) {
const viewerUrl = variantViewers[variantId];
if (viewerUrl) {
iframe.src=viewerUrl;
} else {
console.warn('No viewer URL for this variant');
}
}
updateViewer(variantInput.value);
variantInput.addEventListener('change', function() {
updateViewer(this.value);
});
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.attributeName === 'value') {
updateViewer(variantInput.value);
}
});
});
observer.observe(variantInput, {
attributes: true,
attributeFilter: ['value']
});
});
</script>
<style>
.product-3d-viewer-container {
position: relative;
width: 100%;
max-width: 100%;
aspect-ratio: 1 / 1;
background: transparent;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
.product-3d-viewer-iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
background: transparent;
}
</style>
<!-- 3D Viewer integration end -->
{%- if product.selected_or_first_available_variant.featured_media != null -%}
{%- assign featured_media = product.selected_or_first_available_variant.featured_media -%}
Solved! Go to the solution
This is an accepted solution.
Nevermind, I fixed it!
I replaced:
{%- if product.selected_or_first_available_variant.featured_media != null -%}
{%- assign featured_media = product.selected_or_first_available_variant.featured_media -%}
With:
{%- if product.selected_or_first_available_variant.featured_media != null and product.selected_or_first_available_variant.metafields.custom.viewer == blank -%}
{%- assign featured_media = product.selected_or_first_available_variant.metafields.custom.viewer -%}
Hi,
@media only screen and (max-width: 767px) {
/* Container that holds product media items */
.product__media-list {
display: flex;
flex-direction: column;
}
/* Target the 3D viewer item and move it to the top */
.product__media-item--3dviewer {
order: -1; /* Moves this item to the front */
}
}
document.addEventListener('DOMContentLoaded', function () {
if (window.innerWidth <= 767) {
const mediaList = document.querySelector('.product__media-list');
const viewer = document.querySelector('.product__media-item--3dviewer');
if (mediaList && viewer) {
mediaList.prepend(viewer);
}
}
});
1. Add a unique class to your 3D viewer container in product-media-gallery.liquid:
<li class="product__media-item product__media-item--3dviewer grid__item slider__slide" data-media-id="{{ section.id }}-3dviewer">
<!-- Your 3D viewer embed code here -->
</li>
2. Add the CSS above to your theme’s CSS file or custom CSS section to reorder on mobile.
Hi, I followed your steps but it still doesn't work.😥 Ive attached my code below which is updated if you could take a look.
<!-- 3D Viewer integration start -->
{% assign first_variant = product.variants.first %}
{% if first_variant.metafields.custom.viewer %}
<li class="product__media-item product__media-item--3dviewer grid__item slider__slide is-active" data-media-id="{{ section.id }}-3d-viewer" style="order: -10;">
<div class="product-3d-viewer-container">
<iframe
id="product-viewer-iframe-{{ section.id }}"
src="{{ first_variant.metafields.custom.viewer }}"
class="product-3d-viewer-iframe"
allowfullscreen
loading="lazy"
></iframe>
</div>
</li>
{% endif %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const sectionId = '{{ section.id }}';
const iframe = document.querySelector(`#product-viewer-iframe-${sectionId}`);
const variantInput = document.querySelector(`#product-form-${sectionId} [name="id"]`);
if (window.innerWidth <= 767) {
const mediaList = document.querySelector('.product__media-list');
const viewer = document.querySelector('.product__media-item--3dviewer');
if (mediaList && viewer) {
mediaList.prepend(viewer);
}
}
if (!iframe || !variantInput) return;
const variantViewers = {};
{% for variant in product.variants %}
{% if variant.metafields.custom.viewer %}
variantViewers['{{ variant.id }}'] = '{{ variant.metafields.custom.viewer }}';
{% endif %}
{% endfor %}
function updateViewer(variantId) {
const viewerUrl = variantViewers[variantId];
if (viewerUrl) {
iframe.src=viewerUrl;
} else {
console.warn('No viewer URL for this variant');
}
}
updateViewer(variantInput.value);
variantInput.addEventListener('change', function() {
updateViewer(this.value);
});
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.attributeName === 'value') {
updateViewer(variantInput.value);
}
});
});
observer.observe(variantInput, {
attributes: true,
attributeFilter: ['value']
});
});
</script>
<style>
.product-3d-viewer-container {
position: relative;
width: 100%;
max-width: 100%;
aspect-ratio: 1 / 1;
background: transparent;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
.product-3d-viewer-iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
background: transparent;
}
</style>
<!-- 3D Viewer integration end -->
what did rendering on storefront? you can try to add log to document event then check whether is it running or not?
Yes my 3D viewer is rendering on my store front, it is also the first media on the product page because I gave it a very low sort order. For some reason though on thin screens the image pops up first and I have to scroll back to see the 3D viewer.
Did you resize screen and reload page?
If yes, the code seem not working
if (window.innerWidth <= 767) {
const mediaList = document.querySelector('.product__media-list');
const viewer = document.querySelector('.product__media-item--3dviewer');
if (mediaList && viewer) {
mediaList.prepend(viewer);
}
}
Yea, I did.
can you inspect browser to see whether product__media-item--3dviewer selector above product__media-list selector on mobile?
product__media-item--3dviewer is below roduct__media-list. Could you also inspect my website? https://wznhfa-vh.myshopify.com/
Hi ,
On my phone, I see the 3d viewer display only
Hmm have you tried changing variants? Also, I had another thought. Is it possible to disable scroll to featured image when changing variants in Shopify's script? I think that might fix my problem.
This is an accepted solution.
Nevermind, I fixed it!
I replaced:
{%- if product.selected_or_first_available_variant.featured_media != null -%}
{%- assign featured_media = product.selected_or_first_available_variant.featured_media -%}
With:
{%- if product.selected_or_first_available_variant.featured_media != null and product.selected_or_first_available_variant.metafields.custom.viewer == blank -%}
{%- assign featured_media = product.selected_or_first_available_variant.metafields.custom.viewer -%}
@nhdemas be aware images loading/appearing first in place of 3D models or videos is done on purpose to avoid bogging down mobile devices and thus decreasing conversion rates; READ: you loose money with bad load performance.
Test the performance of such changes excruciatingly carefully .
Contact paull.newton+shopifyforum@gmail.com for the solutions you need
Save time & money ,Ask Questions The Smart Way
Problem Solved? ✔Accept and Like solutions to help future merchants
Answers powered by coffee Thank Paul with a ☕ Coffee for more answers or donate to eff.org
Learn how to build powerful custom workflows in Shopify Flow with expert guidance from ...
By Jacqui May 7, 2025Did You Know? May is named after Maia, the Roman goddess of growth and flourishing! ...
By JasonH May 2, 2025Discover opportunities to improve SEO with new guidance available from Shopify’s growth...
By Jacqui May 1, 2025