Best Practices
Creating strong, effective 3D ecommerce experiences can be tricky. This list below compiles some of the most common pitfalls and challenges we’ve seen in real-world cases, with tips for ensuring the experiences you craft are as user-friendly as possible.
Layouts
Section titled “Layouts”While Dopple is designed to be able to fit anywhere into your site’s frontend, there may be certain layouts where having a 3D interactive canvas could interfere with the overall user experience.
- One such case is in carousels. On mobile devices, where users often use touch gestures to navigate through a carousel, the canvas’s camera controls may override or conflict with the carousel’s behavior: the user may try to touch and drag the 3D product to rotate it, but the carousel begins to swipe to the next item instead.
- Another case is when the canvas takes up the available space that a mobile user would otherwise use for scrolling up and down the page, such as when the canvas covers the full height of the screen. Now, when the user tries to scroll down the page, the touch-and-drag only triggers rotating the product, leaving the user stuck. Ensure users always have a space outside the canvas to touch and scroll the page.
- For classic PDP layouts with the 3D visual on one side and the menu of configuration options on the other, it may be good to use
position: sticky;
on the canvas if the side menu is long so that the 3D product always stays in view even as the user scrolls down the menu to view more options.
Accessibility
Section titled “Accessibility”Be sure to use semantic HTML elements for your UI’s controls, such as <button>
, <input type="radio">
, and <select>
, to help ensure your 3D experience is accessible by all users.
<!-- ❌ Bad --><div onclick="handleUpdateSelection()">Click me</div>
<!-- ✅ Good --><button onclick="handleUpdateSelection()">Click me</button>
Be sure to also use proper ARIA roles and attributes as needed across your page to allow users relying on assistive technologies to access all parts of your configurator’s UI.
Lazy Loading Indicators
Section titled “Lazy Loading Indicators”For products that are set up to lazily load new options, users on slower connections may see a delay between selecting a new option and seeing it visually update on the 3D render. During this time, it may be useful to show a loading indicator in the UI to signal that their choice is being loaded.
To do this, you can utilize the async nature of updateSelection()
to display the indicator until it resolves.
myButton.addEventListener("click", async () => { showLoadingIndicator(); await dopple.updateSelection(/* new selection here*/); hideLoadingIndicator();});
Some UIs may choose to show a loading spinner over the entire configurator, over the menu of options itself, or over just the individual option that was chosen — the choice is up to you!
Useful Loading Screens
Section titled “Useful Loading Screens”One of the most commonly missed opportunities is not utilizing your loading time to its fullest. Users may be at risk of leaving early if they perceive the wait to be too long, but giving them something useful to see while they wait can work wonders for your overall user experience and retention.
In addition to showing a loading progress bar or percentage, some creative ideas our clients have used include:
- Showing a static 2D placeholder image of the 3D product that is about to be displayed.
- Showing an “intro” screen instead of a loader while the Dopple product loads in the background, which can include a welcome message or preset configurations to choose from.
- Showing a rotating series of messages, such as “did you know?” facts about the product.
- Showing icons for the interaction controls, similar to these ones below:
Tip: use the @media (hover: hover)
media query to show either the hand or mouse icons according to the device the user is on (see the CSS code snippets below).
<div class="config-loading-controls"> <div class="config-loading-control--touch"> <div class="config-loading-control-icon"> <img src="icon-hand-rotate.svg" alt="Touch and drag to rotate" /> Rotate </div> <div class="config-loading-control-icon"> <img src="icon-hand-pan.svg" alt="Drag with two fingers to pan" /> Pan </div> <div class="config-loading-control-icon"> <img src="icon-hand-zoom.svg" alt="Pinch to zoom" /> Zoom </div> </div> <div class="config-loading-control--mouse"> <div class="config-loading-control-icon"> <img src="icon-mouse-rotate.svg" alt="Click and drag to rotate" /> Rotate </div> <div class="config-loading-control-icon"> <img src="icon-mouse-pan.svg" alt="Right-click to pan" /> Pan </div> <div class="config-loading-control-icon"> <img src="icon-mouse-zoom.svg" alt="Scroll to zoom" /> Zoom </div> </div></div>
.config-loading-control--touch,.config-loading-control--mouse { align-items: center; color: #50535a; display: flex; font-size: 12px; gap: 2rem; justify-content: center; position: relative; text-align: center; user-select: none; z-index: 1;}.config-loading-control--mouse { display: none;}@media (hover: hover) { .config-loading-control--touch { display: none; } .config-loading-control--mouse { display: flex; }}.config-loading-control-icon { font-size: 0.875rem; width: 3rem;}@media (min-width: 40rem) { .config-loading-control-icon { width: 4rem; }}
Fullscreen Mode
Section titled “Fullscreen Mode”Add a “fullscreen” button to the bottom corner of your canvas to enter and exit fullscreen mode. Here, the button will contain two SVG icons indicating if fullscreen mode is active or inactive, which will be shown or hidden using CSS via the data-fullscreen-state
attribute’s value.
<!-- The main wrapper element will be sent into fullscreen mode --><div class="wrapper">
<!-- Dopple canvas --> <div class="dopple-container"></div>
<!-- Fullscreen button --> <button class="fullscreen-button" title="Enter Fullscreen" data-fullscreen-state="inactive">
<!-- "Active" fullscreen icon --> <svg data-fullscreen-icon="inactive" width="24" height="24" viewBox="0 0 24 24"> <use xlink:href="#icon--fullscreen" class="line-shadow line-shadow--fullscreen" /> <g id="icon--fullscreen"> <path d="M4 8v-2a2 2 0 0 1 2 -2h2" /> <path d="M4 16v2a2 2 0 0 0 2 2h2" /> <path d="M16 4h2a2 2 0 0 1 2 2v2" /> <path d="M16 20h2a2 2 0 0 0 2 -2v-2" /> </g> </svg>
<!-- "Inactive" fullscreen icon --> <svg data-fullscreen-icon="active" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> <use xlink:href="#icon--fullscreen-close" class="line-shadow" /> <g id="icon--fullscreen-close"> <path d="M16 20v-2a2 2 0 0 1 2 -2h2" /> <path d="M16 4v2a2 2 0 0 0 2 2h2" /> <path d="M4 16h2a2 2 0 0 1 2 2v2" /> <path d="M4 8h2a2 2 0 0 0 2 -2v-2" /> </g> </svg> </button></div>
.wrapper { aspect-ratio: 16 / 10; display: grid; position: relative;}.wrapper > * { grid-area: 1 / 1; /* Position the fullscreen button over the canvas */}.fullscreen-button { align-items: center; background: none; border: 0; cursor: pointer; display: flex; font-family: inherit; justify-content: center; margin: 0.5rem; padding: 0.25rem; place-self: end;}[data-fullscreen-icon] { fill: none; height: 1.5rem; max-width: unset; stroke: #FFF; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2; width: 1.5rem;}.line-shadow { opacity: 0.45; stroke: #000; stroke-width: 4; transition: opacity 150ms ease;}.fullscreen-button:hover .line-shadow,.fullscreen-button:focus .line-shadow { opacity: 0.75;}[data-fullscreen-state="inactive"] [data-fullscreen-icon="active"],[data-fullscreen-state="active"] [data-fullscreen-icon="inactive"] { display: none; /* Show/hide fullscreen icons according to fullscreen state */}
const wrapper = document.querySelector(".wrapper");const fullscreenButton = wrapper.querySelector(".fullscreen-button");
function toggleFullscreen() { if (document.fullscreenElement || document.webkitFullscreenElement) { document.exitFullscreen(); document.webkitExitFullscreen(); fullscreenButton.dataset.fullscreenState = "inactive"; fullscreenButton.title = "Enter Fullscreen"; } else { wrapper.requestFullscreen(); wrapper.webkitRequestFullscreen(); fullscreenButton.dataset.fullscreenState = "active"; fullscreenButton.title = "Exit Fullscreen"; }}
fullscreenButton.addEventListener("click", toggleFullscreen);