360 Degrees Product Viewer

360 Degrees Product Viewer

A simple, interactive resource that can be used to provide a virtual tour of your product.

Nucleo icons

Sponsored by Nucleo, a free application to collect, customize and export all your icons as icon font and SVG symbols. Made by the CodyHouse folks!

All the resources available on CodyHouse are released under the BSD-3-Clause license. You can support our project with a Paypal donation 🙌

In e-commerce design, one of the main goals is to fill the gap between product and user. This is particularly relevant for high-priced goods. Hence, the importance to integrate interactive tools, to provide the user a way to “virtually experience” the product.

Today’s resource is a simple, interactive resource that can be used to show a virtual tour of the product. The idea behind the snippet is to use an image sprite and link the dragging to a specific frame of that image. You can use it to show the exterior of a technology gadget (or a car, like in our demo!), or, in general, to create fancy product animations.

Photo credits: Alfa Romeo.

Creating the structure

The HTML structure is composed of two main elements: a figure.product-viewer for the image sprite and the product preview image, and a div.cd-product-viewer-handle for the viewer handle.

<div class="cd-product-viewer-wrapper" data-frame="16" data-friction="0.33">
   <div>
      <figure class="product-viewer">
         <img src="img/product-loading.jpg" alt="Product Preview">
         <div class="product-sprite" data-image="img/product.png"></div>
      </figure> <!-- .product-viewer -->

      <div class="cd-product-viewer-handle">
         <span class="fill"></span>
         <span class="handle">Handle</span>
      </div>
   </div> <!-- .cd-product-viewer-handle -->
</div> <!-- .cd-product-viewer-wrapper -->

The data-frame attribute of the div.cd-product-viewer-wrapper specifies the number of frames the image sprite is composed of, while the data-friction specifies the friction while dragging on the image (it has to be greater than zero).

Adding style

The <img> element is visible only at the beginning, while the image sprite is still loading, and is used to give the proper dimensions to the figure.product-viewer element.
As for the div.product-sprite, it has an absolute position, a height of 100% and width of 1600% (our image sprite is composed of 16 frames) and is hidden by default. The .loaded class is then used to show the div.product-sprite once the image sprite has been loaded:

.cd-product-viewer-wrapper .product-viewer {
  position: relative;
  overflow: hidden;
}
.cd-product-viewer-wrapper img {
  /* this is the image visible before the image sprite is loaded */
  display: block;
  position: relative;
  z-index: 1;
}
.cd-product-viewer-wrapper .product-sprite {
  position: absolute;
  z-index: 2;
  top: 0;
  left: 0;
  height: 100%;
  /* our image sprite is composed of 16 frames */
  width: 1600%;
  background: url(../img/product.png) no-repeat center center;
  background-size: 100%;
  opacity: 0;
  transition: opacity 0.3s;
}
.cd-product-viewer-wrapper.loaded .product-sprite {
  /* image sprite has been loaded */
  opacity: 1;
}

When the user drags the span.handle or the product image, we change the div.product-sprite translateX value to show a different image frame (using JavaScript).

Note: the frames composing your image sprite should have the same aspect ratio of the product preview image.

The handle loading effect is achieved by changing the scaleX value of the span.fill element (using JavaScript); once the image sprite has been loaded, the span.fill is hidden and the span.handle is shown:

.cd-product-viewer-handle {
  position: relative;
  z-index: 2;
  width: 60%;
  max-width: 300px;
  height: 4px;
  background: #4d4d4d;
}
.cd-product-viewer-handle .fill {
  /* this is used to create the loading fill effect */
  position: absolute;
  z-index: 1;
  left: 0;
  top: 0;
  height: 100%;
  width: 100%;
  border-radius: inherit;
  background: #b54240;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.5s;
}
.loaded .cd-product-viewer-handle .fill {
  /* image sprite has been loaded */
  opacity: 0;
}
.cd-product-viewer-handle .handle {
  position: absolute;
  z-index: 2;
  display: inline-block;
  height: 44px;
  width: 44px;
  left: 0;
  top: -20px;
  background: #b54240 url(../img/cd-arrows.svg) no-repeat center center;
  border-radius: 50%;
  transform: translateX(-50%) scale(0);
}
.loaded .cd-product-viewer-handle .handle {
  /* image sprite has been loaded */
  transform: translateX(-50%) scale(1);
  animation: cd-bounce 0.3s 0.3s;
  animation-fill-mode: both;
}
@keyframes cd-bounce {
  0% {
    transform: translateX(-50%) scale(0);
  }
  60% {
    transform: translateX(-50%) scale(1.1);
  }
  100% {
    transform: translateX(-50%) scale(1);
  }
}

Events handling

To implement the product viewer, we created a productViewer object and used the loadFrames method to check whether the image sprite has been loaded:

var productViewer = function(element) {
   this.element = element;
   this.handleContainer = this.element.find('.cd-product-viewer-handle');
   this.handleFill = this.handleContainer.children('.fill');
   //...
   this.frames = this.element.data('frame');
   //increase this value to increase the friction while dragging on the image - it has to be bigger than zero
   this.friction = this.element.data('friction');
   this.visibleFrame = 0;
   this.loaded = false;
   //...
   this.loadFrames();
} 

productViewer.prototype.loadFrames = function() {
   var self = this,
       imageUrl = this.slideShow.data('image');
   //you need this to check if the image sprite has been loaded
   $('<img/>').attr('src', imageUrl).load(function() {
      self.loaded = true;
   });

   this.loading('0.5'); //triggers loading animation
}

var productToursWrapper = $('.cd-product-viewer-wrapper');
productToursWrapper.each(function(){
   new productViewer($(this));
});

Once the image sprite has been loaded, we attach an event handler for the mousedown/mousemove/mouseup events to the proper elements:

if( self.loaded ){
   //sprite image has been loaded
   self.element.addClass('loaded');
   self.dragImage();
   self.dragHandle();
   //..
} else {
   //...
}

For this effect to work on touch devices, we used the vmousedown/vmousemove/vmouseup events provided by the jQuery mobile framework.

Join our newsletter

Get our monthly recap with the latest CodyHouse news