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.