{% liquid
assign ProductVRSources = ''
assign ProductVRSourcesIos = ''
assign ProductVRAlts = ''
assign ProductVRPosters = ''
assign isFirstModel = false
for model in section.settings.product_vr.media
if model.media_type == 'model'
unless isFirstModel
assign firstModel = model
assign isFirstModel = true
endunless
assign ProductVRSources = ProductVRSources | append: ',' | append: model.sources[0].url
assign ProductVRSourcesIos = ProductVRSourcesIos | append: ',' | append: model.sources[1].url
assign ProductVRAlts = ProductVRAlts | append: ',' | append: model.alt
assign modelImg = model.preview_image | image_url
assign ProductVRPosters = ProductVRPosters | append: ',' | append: modelImg
endif
endfor
%}
{{
firstModel
| model_viewer_tag: shadow-intensity: 1, ar: true, camera-controls: true, touch-action: 'pan-y', id: 'main-viewer'
}}
<button slot="ar-button" id="ar-button">View in your space</button>
<div id="ar-prompt">
<img src="{{ "hand.png" | asset_url }}">
</div>
<button id="ar-failure">AR is not tracking!</button>
<div class="slider" id="slider-model-viewer">
<div class="slides">
{% assign posters = ProductVRPosters | split: ',' %}
{% for poster in posters %}
{% unless forloop.index0 == 0 %}
<button
class="slide{% if forloop.index0 == 1 %} selected{% endif %}"
onclick="switchSrc(this, {{ forloop.index0 }})"
style="background-image: url('{{ poster }}');"
></button>
{% endunless %}
{% endfor %}
</div>
</div>
<script async type="module">
document.addEventListener('DOMContentLoaded', (event) => {
const modelViewer = document.querySelector('#main-viewer');
const sliders = document.querySelector('#slider-model-viewer');
const arBtn = document.querySelector('#ar-button');
const arPrompt = document.querySelector('#ar-prompt');
const arFailure = document.querySelector('#ar-failure');
modelViewer.append(arBtn);
modelViewer.append(arPrompt);
modelViewer.append(arFailure);
modelViewer.append(sliders);
const sources = '{{ ProductVRSources }}'.split(',').filter((item) => item != '');
const iosSources = '{{ ProductVRSourcesIos }}'.split(',').filter((item) => item != '');
const alts = '{{ ProductVRAlts }}'.split(',').filter((item) => item != '');
const posters = '{{ ProductVRPosters }}'.split(',').filter((item) => item != '');
window.switchSrc = (element, pos) => {
const position = pos - 1;
modelViewer.src = sources[position];
modelViewer.poster = posters[position];
modelViewer.iosSrc = iosSources[position];
const slides = document.querySelectorAll('#main-viewer .slide');
slides.forEach((element) => {
element.classList.remove('selected');
});
element.classList.add('selected');
};
modelViewer.addEventListener('load', (event) => {
document.querySelector('#main-viewer .slider').addEventListener('beforexrselect', (ev) => {
ev.preventDefault();
});
});
});
</script>
{% schema %}
{
"name": "Product VR Preview",
"tag": "section",
"class": "product-vr-wrapper",
"settings": [
{
"type": "product",
"id": "product_vr",
"label": "Product VR Models To Showcase"
}
],
"presets": [
{
"name": "Product VR Preview"
}
]
}
{% endschema %}
{% stylesheet %}
/* This keeps child nodes hidden while the element loads */
:not(:defined) > * {
display: none;
}
model-viewer {
background-color: #eee;
overflow-x: hidden;
height: 80vh;
width: 80%;
margin: 0 auto;
}
#ar-button {
background-image: url({{'ic_view_in_ar_new_googblue_48dp.png' | asset_url }});
background-repeat: no-repeat;
background-size: 20px 20px;
background-position: 12px 50%;
background-color: #fff;
position: absolute;
left: 50%;
transform: translateX(-50%);
white-space: nowrap;
bottom: 132px;
padding: 0px 16px 0px 40px;
font-family: Roboto Regular, Helvetica Neue, sans-serif;
font-size: 14px;
color: #4285f4;
height: 36px;
line-height: 36px;
border-radius: 18px;
border: 1px solid #dadce0;
}
#ar-button:active {
background-color: #e8eaed;
}
#ar-button:focus {
outline: none;
}
#ar-button:focus-visible {
outline: 1px solid #4285f4;
}
@keyframes circle {
from {
transform: translateX(-50%) rotate(0deg) translateX(50px) rotate(0deg);
}
to {
transform: translateX(-50%) rotate(360deg) translateX(50px) rotate(-360deg);
}
}
@keyframes elongate {
from {
transform: translateX(100px);
}
to {
transform: translateX(-100px);
}
}
model-viewer > #ar-prompt {
position: absolute;
left: 50%;
bottom: 175px;
animation: elongate 2s infinite ease-in-out alternate;
display: none;
}
model-viewer[ar-status='session-started'] > #ar-prompt {
display: block;
}
model-viewer > #ar-prompt > img {
animation: circle 4s linear infinite;
}
model-viewer > #ar-failure {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 175px;
display: none;
}
model-viewer[ar-tracking='not-tracking'] > #ar-failure {
display: block;
}
.slider {
width: 100%;
text-align: center;
overflow: hidden;
position: absolute;
bottom: 16px;
}
.slides {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
}
.slide {
scroll-snap-align: start;
flex-shrink: 0;
width: 100px;
height: 100px;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
background-color: #fff;
margin-right: 10px;
border-radius: 10px;
border: none;
display: flex;
}
.slide.selected {
border: 2px solid #4285f4;
}
.slide:focus {
outline: none;
}
.slide:focus-visible {
outline: 1px solid #4285f4;
}
{% endstylesheet %}
Preview:
downloadDownload PNG
downloadDownload JPEG
downloadDownload SVG
Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!
Click to optimize width for Twitter