E-commerce user experience: product carousel

Preview final AR scene

In this tutorial we will learn how to create a shop-like experience for a sneaker business using Studio’s WebAR capabilities and the customization options provided through the code editor. You can test the result yourself through this QR code:

Trainars marker and QR

This experience consists of an augmented reality scene built with Onirix Studio that will contain a 3D model of every product in the shop, a label associated with each model containing the details of the product and an image to use as background.

This scene will be consumed by the user by reading a QR code or navigating to the scene’s URL. Additionally to the scene, we need to create the graphical user interface that the client will use to control the scene and purchase the products. This will be achieved using Studio’s code editor which makes it possible to embed new HTML, CSS and JavaScript code over the AR scene defined previously. In this case, we will need to create:

  • Two arrow buttons to navigate between the products.
  • An “Add to cart” button for selecting products.
  • And a shopping cart button that can redirect to the shop's purchase process.

The final product will look like this:

Trainars product

Required assets

To built an equal web AR experience you will need:

  • Two 3D models of the example products. You can look for models in services such as Sketchfab. In this example, these two models were used: Nike, Adidas.
  • One image to use as a marker.
  • One image to use as a cover for the marker.

Step 1: Create the AR scene in Studio

Firstly, we need to log in Studio (or sign up if we do not have an account yet) and create a new project. After choosing a name, for example “Trainars”, we select the option to create a new image tracking scene. In the next dialog, we upload the marker that will be used to start the AR experience when read by the camera.

Upload a marker

Once we are in the scene editor, in the bottom right corner of the screen, select “Add new” to start importing the assets needed to define the experience. You can also do this from the Assets page and, as we did for the tutorial, organize all the assets of this project under the same collection. For this example, we create at least three new assets: two 3D models of sneakers and the image that will be shown behind the 3D models as background.

Our first addition to the scene will be the aforementioned image. In order to do this, search for the asset in the bottom bar and drag it to the scene. Once it is rendered, use the scene controls to place it exactly above the marker and give it the same size.

Placed maker cover

Following this, we will drag and drop the first 3D model in the centre of the augmented reality scene and use the controls to rotate it and give it a size that does not overflow the cover too much.

Placed sneaker

Now we need to add a label that will show the details of the selected product when clicked. To achieve this, select the “Label” tab on the asset library and drag one to the scene. In this case, we have chosen to place it above the 3D model and to give it a navy blue colour.

Placed details label

After placing it, we provide the information of the product placed below it. To do this, Studio provides “Content” property where we can define a title, an image, a description and an URL to open. To add this content, with the recently placed label selected, look at the properties sidebar, search for the Content section and select add. A dialog will open and you will be able to define that information on the aforementioned fields. Don’t forget to save it before closing.

Content property

Once that is done, we will have a background image, one 3D model of a product and a label which shows the information about it. Taking into account that this two elements are related and we will want to show them and hide them at the same time, we can use Studio's association capabilities to nest the label inside the sneaker. This will make the label's position relative to the sneaker and it will allow us to disable both elements with just the oid of the model. To achieve this, in the elements list on the left the screen, drag the label element and drop it over the sneaker element in the same list. Alternatively, like in this example, we can create a collection by clicking on the folder icon above the element list and add both the label and the 3D model to it.

Asset collection

We will need more than one pair of sneakers to show the whole behaviour of a shop. We can duplicate the already created collection and then change the names, the label's content and replace the model in the copy through the “Replace” button on the “Element info” section on the “Properties” sidebar. If the models have different sizes, we will need to resize and move the second one so it has more or less the same placement as the first.

Duplicate collection

Finally, as our shop will have a product carousel, only one product must be visible each time. To achieve this, we will toggle the “Enable” option on the “Display” properties to false for the second collection (we only need to do this for the parent element). On the third step we will learn how to control these properties so the product carousel can be built.

Our last action in Studio will be to make sure the project is public so it can be shared and started from any dispositive. For this, click the “Share” option on the top right corner and check that the “Public” toggle is active.

Step 2: Code Editor: design the user interface

Now that we have already constructed the AR scene, we will need to define the elements of the user interface that will be used to control the AR experience. Both the design and the functionality will be set up using Studio’s code editor. To open it, select the “Settings” option on the top right corner and click on “Open code editor”.

The code editor is divided in three sections: HTML, CSS and JavaScript. In this step we will focus on the HTML elements and the styles applied to them.

We will define two main sets of elements: a landing page and the controls of the product carousel. The former will be loaded when the experience starts, will show the image marker that has to be detected by the camera and will disappear when the scene is loaded. The latter will be hidden by default and only show up when the scene loads and the products are shown. It will consist of a top bar with a logo and the shopping cart information and a section at the bottom that will control the carousel behaviour (move between products) and adding/removing from the shopping cart.

We will start working on the first section. We can define a container div that will host two images (a logo at the bottom and the marker to serve as a guide) and another div at the top with the needed instructions.

<!-- landing screen -->
<div id="trainars-landing-page">
    <div class="trainars-landing-page__note">
        <h1>Look for this image to start the experience</h1>
    </div>
    <img src="https://www.onirix.com/trainars/trainars-marker.jpg" alt="Marker preview" class="trainars-landing-page__marker" />
    <img src="https://www.onirix.com/trainars/trainars-white.svg"
        alt="Trainars logo" class="trainars-landing-page__logo" /> 
</div>
<!-- landing screen -->

For their styles, we will give the container almost the whole width (so we do not overlap with the AR player’s menu) and almost all the height. Position will be absolute and we will set it up as a flex column so the elements are in order vertically and are easily centred and spaced.

#trainars-landing-page {
    position: absolute;
    top: 50px;
    left: 0;
    bottom: 20px;
    width: calc(100vw - 60px);
    height: calc(100vh - 125px);
    padding: 0px 30px 20px 30px;

    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-between;
}

The instruction note will be a white title inside a blue rectangle with some padding.

.trainars-landing-page__note {
    color: white;
    background-color: #3316d8;
    opacity: 0.9;

    height: 70px;
    width: 300px;
    padding: 10px 20px;
}

.trainars-landing-page__note h1 {
    text-align: center;
    font-size: 18px;
    font-weight: 600;
    font-family: Poppins;
}

Below it we will give the marker image low opacity so it does not cover the camera feed. At last, we define the size of the shop logo at the bottom.

.trainars-landing-page__marker {
    width: 300px;
    height: 262px;
    opacity: 0.4;
    object-fit: contain;
}

.trainars-landing-page__logo {
    width: 87px;
    height: 20px;
    object-fit: contain;
}

The user will see these elements just after reading the QR code and they will need to disappear just as the marker image is captured and the scene loads. Conversely, the controls will need to be hidden until that loading ends. To achieve this, we will define a class “hidden” that will set any element affected by it to “display: none”.

.hidden {
   display: none !important;
}

The UI elements in charge of controlling the app’s behaviour will be divided in two div. One header bar with the logo and the shopping bag count and one at the bottom with two buttons to control the navigation and one to add the sneakers to the bag. Both of them will be initialised with the “hidden” class. We will start with the header:

<div id="trainars-header" class="hidden">
    <img src="https://www.onirix.com/trainars/trainars-logo.svg" 
        alt="Trainars logo" />
    <button class="trainars-header__bag" title="Shopping bag">
        <span></span>
    </button>
</div>

It will only consist of an img tag and the button for the bag with a span element where we will put the number of items added.

Next, we define the controls with a title, two buttons with arrow icons and two buttons for the bag behaviour (one for adding and one initially hidden for when it is already added). The left and right buttons will use the same arrow image and we will rotate one of them with CSS.

<div id="trainars-controls" class="hidden">
    <div class="trainars-controls__btns">
        <button id="trainars-previous" class="trainars-arrow-btn" title="Previous">
            <img src="https://www.onirix.com/trainars/nav-arrow.svg" />
        </button>
        <button id="trainars-add-item" class="trainars-cart-btn">
            Add to cart <img src="https://www.onirix.com/trainars/cart-add.svg" />
        </button>
        <button id="trainars-added" class="trainars-cart-btn hidden">
            Added <img src="https://www.onirix.com/trainars/cart-added.svg" />
        </button>
        <button id="trainars-next" class="trainars-arrow-btn" title="Next">
            <img src="https://www.onirix.com/trainars/nav-arrow.svg" />
        </button>
    </div>
    <h1 class="trainars-controls__name"></h1>
</div>

Once the elements are defined, we set up the styles. The header will have position absolute at top 0 and left 0 so it is fixed on the top left of the screen. We will give it 55px of height, some padding on the left and less than the whole width so it does not overlap with Studio’s menu.

#trainars-header {
    position: absolute;
    width: calc(100vw - 65px);
    height: 55px;
    top: 0;
    left: 0;
    padding-left: 20px;

    display: flex;
    align-items: center;
    justify-content: space-between;
}

The bag will be a 25x25 button with the icon as a centred background without padding and borders. The item counter will be white text with 10px of size.

.trainars-header__bag {
    display: flex;
    align-items: center;
    justify-content: center;

    height: 25px;
    width: 25px;

    background-size: contain;
    background-repeat: no-repeat;
    background-image: url('https://www.onirix.com/trainars/cart.svg');
    background-color: transparent;
    background-position: center;

    padding: 0;
    border: none;
}

.trainars-header__bag span {
    color: white;
    font-size: 10px;
    font-weight: 600;
    text-align: center;
    font-family: Poppins;
    margin-top: 8px;
}

Finally, we set up the styles for the controls. The container will be fixed at the bottom, occupy the whole width and have 100px of height with an orange background. However, the buttons will be pushed half their height over the background and the title will be below them with all the available space as separation. Furthermore, the div will be initially placed under the viewport and will crawl in with a 0.2s transition.

#trainars-controls {
    position: fixed;
    bottom: -128px;
    left: 0;
    height: 100px;
    width: 100vw;
    background-color: #ff7047;

    display: flex !important;
    flex-direction: column;
    align-items: center;
    justify-content: space-between;

    transition: bottom .2s cubic-bezier(.3,.83,.87,.91);
}

#trainars-controls:not(.hidden) {
    bottom: 0;
}

.trainars-controls__btns {
    position: relative;
    height: 56px;
    width: 100vw;
    top: -28px;

    display: flex;
    align-items: center;
    justify-content: space-evenly;
}

The direction buttons will be white squares with the arrow inside them. In the case of the right one, we will rotate 180deg the image so it matches the direction.

.trainars-controls__btns .trainars-arrow-btn {
    height: 56px;
    width: 56px;
    padding: 16px;
    background-color: white;
    border: none;
}

.trainars-arrow-btn img {
    height: 24px;
    width: 24px;
    object-fit: contain;
}

#trainars-next img {
    transform: rotate(180deg);
}

Then, both cart buttons will have a 30x30 icon next to the white bold text. The add-item one will have a navy blue background and the added one a bright green. Finally, we give the heading some padding so it is not touching the bottom of the viewport, we set its colour to a blackish one and its size to 21.6px.

.trainars-cart-btn {
    height: 56px;
    width: 187px;
    padding: 13px 18px;
    background-color: #3316d8;
    color: white;
    font-weight: bold;
    font-family: Poppins;
    font-size: 19px;
    border: none;
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.trainars-cart-btn img {
    height: 30px;
    width: 30px;
    object-fit: contain;
}

#trainars-added {
    background-color: #00897b;
}

.trainars-controls__name {
    color: #1a0904;
    margin: 0;
    padding-bottom: 25px;
    font-family: Poppins;
    font-size: 21.6px;
    font-weight: bold;
    text-align: center;
}

We also need to take into account that, as it is, the controls will hide part of the content defined in the details label. This can also happen to the player’s context menu. To fix this we will need to increase their z-index. Additionally, we can hide the WebAR player’s buttons and messages that we will not be using.

#webar-context-menu,
#panel {
   z-index: 2;
}

#webar-bubble-message {
   visibility: hidden;
}

#webar-fullscreen-button,
#webar-markers-button,
#webar-markers-bubble {
   display: none;
}

Step 3: Code Editor: define the behaviour

Now that we have set up the user interface design, the only thing remaining is giving its elements the expected behaviour. For this task we will add some functions to the JavaScript section of the code editor.

The first step will be to define a data structure that stores the needed information about the items added to the AR scene. For the example, we will create an array with two objects representing both sneaker pairs available for purchase. Their properties will be:

  • oid: the id that identifies the collection with the model of the sneaker and its label inside the AR player.
  • label: the name of the product that will be shown in the heading inside the controls div.
  • added: a boolean flag so we can know if the item was added to the shopping cart.

A variable keeping track of the currently selected index will also be needed.

You will need to update the oid's inside the code editor when you copy or duplicate the project.

const items = [
    { 
        oid: '43b19f7b1add4137a5f171ca909f62ed',
        label: 'Nike Air max 90',
        added: false 
    },
    { 
        oid: '42f4235c9b174cd88852801843990b54',
        label: 'Adidas neo',
        added: false
    }
];

let currentIndex = 0;

Secondly, we will need to import and set up the Onirix embed SDK in order to be able to listen to the AR player events and react to them.

const script = document.createElement('script');
script.src = "https://sdk.onirix.com/embed/ox-embed-sdk.umd.js";
script.onload = async () => {
   const embedSDK = new OnirixEmbedSDK();
   await embedSDK.connect();
};
document.head.appendChild(script);

Inside the onload, after waiting for the SDK to connect, we need to set up the event listeners for all the items that interact with the scene and subscribe to the SCENE LOAD END and SCENE LOST events so we can know what set of HTML elements need to be shown (the landing or the controls). The behaviour associated with them will be defined outside in some named functions. Additionally, we can initialise the add buttons listeners too. It will look something like this:

script.onload = async () => {
   const embedSDK = new OnirixEmbedSDK();
   await embedSDK.connect();

   const prevBtn = document.querySelector('#trainars-previous');
    prevBtn.addEventListener('click', () => getItem(embedSDK, false));

    const nextBtn = document.querySelector('#trainars-next');
    nextBtn.addEventListener('click', () => getItem(embedSDK, true));

    const addBtn = document.querySelector('#trainars-add-item');
    addBtn.addEventListener('click', toggleItem);

    const addedBtn = document.querySelector('#trainars-added');
    addedBtn.addEventListener('click', toggleItem);

    embedSDK.subscribe(OnirixEmbedSDK.Events.SCENE_LOAD_END, (params) => {
        changeName(items[currentIndex]);
        checkAdded(items[currentIndex]);
        changeScreen();
    });

    embedSDK.subscribe(OnirixEmbedSDK.Events.SCENE_LOST, (params) => {
        changeScreen();
    });
};

Finally, we will define those methods. First, we will start with changeScreen. It will be a simple function that will get the classList of the #trainars-landing-page, #trainars-header and #trainars-controls elements and toggle the hidden class when the scene is loaded or lost.

function changeScreen() {
   const toggleHide = (id) => document.querySelector(id).classList.toggle('hidden');
   toggleHide('#trainars-landing-page');
   toggleHide('#trainars-header');
   toggleHide('#trainars-controls');
}

When the scene is loaded, we will also need to set up the control title to the name of the product being shown and check if it was already added to the shopping cart. We define two simple functions to achieve this, one gets both add buttons and shows one or the other depending on the item state. The other just gets the heading and sets its innerText to the label of the current product.

function checkAdded(item) {
    const add = document.querySelector('#trainars-add-item');
    const added = document.querySelector('#trainars-added');
    if (item.added) {
        added.classList.remove('hidden');
        add.classList.add('hidden');
    } else {
        added.classList.add('hidden');
        add.classList.remove('hidden');
    }
}

function changeName(currentItem) {
   const name = document.querySelector('.trainars-controls__name');
   name.innerText = currentItem.label;
}

Next, we add the add/remove functionality of the add buttons in a single function that changes the added property of the currently selected product, calls the checkAdded function defined before to show the correct button and calculates the total number of products with the added flag set to true in order to show it in the shopping cart.

function toggleItem() {
   items[currentIndex].added = !items[currentIndex].added;
   checkAdded(items[currentIndex]);

   const bag = document.querySelector('.trainars-header__bag span');
   const addedItems = items.filter(i => i.added).length;
   bag.textContent = addedItems ? addedItems : '';
}

Finally, we define the function to navigate between items using the arrow buttons. It will need to call the SDK to disable the currently shown item using the oid stored on the items array. Then, after checking the length of the array and updating the currentIndex, it will call the SDK to make the corresponding item and its label visible. It will also need to call the checkName and checkAdded functions to update the UI elements.

function getItem(sdk, direction) {
   let currentItem = items[currentIndex];
   sdk.disable(currentItem.oid);

   currentIndex += direction ? 1 : -1;
   if (currentIndex >= items.length) {
       currentIndex = 0;
   } else if (currentIndex < 0) {
       currentIndex = items.length - 1;
   }

   currentItem = items[currentIndex];
   sdk.enable(currentItem.oid);
   changeName(currentItem);
   checkAdded(currentItem);
}

Step 4: Test the Web AR experience!

Now that everything is correctly defined and set up, the only task remaining is reading the QR code in the “Share” tab to access the project and play around with it to see if it works properly.

In order to easily scan your marker and start the scene, you can click the “Preview” option on the top right corner. Furthermore, clicking the "Download" button under "Download markers with integrated QR" option will generate an edit of the marker image with the QR of the scene integrated in it. This file allows you to open the player and start the scene with the same image.

Trainars start screen Trainars product Trainars product added

Results