Product Grid Layout

A responsive product grid layout with some UI details for inspiration.

ProductGridLayout

View demo Download source

A responsive product grid layout that comes with some UI details for inspiration. The product will rotate showing the backside of the item when the rotate button is clicked. Some examples of how tooltips can appear on hover or click are also included. Media queries can be used to resize the items in the grid or change the number of items in a row. Flexbox is used when supported.

The HTML

<div id="cbp-pgcontainer" class="cbp-pgcontainer">
	<ul class="cbp-pggrid">
		<li>
			<div class="cbp-pgcontent">
				<span class="cbp-pgrotate">Rotate Item</span>
				<div class="cbp-pgitem">
					<div class="cbp-pgitem-flip">
						<img src="images/1_front.png" />
						<img src="images/1_back.png" />
					</div>
				</div><!-- /cbp-pgitem -->
				<ul class="cbp-pgoptions">
					<li class="cbp-pgoptcompare">Compare</li>
					<li class="cbp-pgoptfav">Favorite</li>
					<li class="cbp-pgoptsize">								
						<span data-size="XL">XL</span>
						<div class="cbp-pgopttooltip">
							<span data-size="XL">XL</span>
							<span data-size="L">L</span>
							<span data-size="M">M</span>
							<span data-size="S">S</span>
						</div>
					</li>
					<li class="cbp-pgoptcolor">
						<span data-color="c1">Blue</span>
						<div class="cbp-pgopttooltip">
							<span data-color="c1">Blue</span>
							<span data-color="c2">Pink</span>
							<span data-color="c3">Orange</span>
							<span data-color="c4">Green</span>
						</div>
					</li>
					<li class="cbp-pgoptcart"></li>
				</ul><!-- cbp-pgoptions -->
			</div><!-- cbp-pgcontent -->
			<div class="cbp-pginfo">
				<h3>Save my trees</h3>
				<span class="cbp-pgprice">$29</span>
			</div>
		</li>
		<li>
			<!-- ... -->
		</li>
		<li>
			<!-- ... -->
		</li>
	</ul><!-- /cbp-pggrid -->
</div><!-- /cbp-pgcontainer -->

The CSS

@font-face {
	font-family: 'pgicons';
	src:url('../fonts/pgicons/pgicons.eot');
	src:url('../fonts/pgicons/pgicons.eot?#iefix') format('embedded-opentype'),
		url('../fonts/pgicons/pgicons.woff') format('woff'),
		url('../fonts/pgicons/pgicons.ttf') format('truetype'),
		url('../fonts/pgicons/pgicons.svg#pgicons') format('svg');
	font-weight: normal;
	font-style: normal;
}

.cbp-pgcontainer {
	position: relative;
	width: 100%;
	padding: 0 30px 100px 30px;
}

.cbp-pgcontainer ul,
.cbp-pgcontainer li {
	padding: 0;
	margin: 0;
	list-style-type: none;
}

.cbp-pggrid {
	position: relative;
	text-align: center;
}
/* If flexbox is supported we'll use it to lay out the grid */
.flexbox .cbp-pggrid {
	display: -webkit-flex;
	display: -moz-flex;
	display: -ms-flex;
	display: flex;
	-webkit-flex-flow: row wrap;
	-moz-flex-flow: row wrap;
	-ms-flex-flow: row wrap;
	flex-flow: row wrap;
	-webkit-justify-content: center;
	-moz-justify-content: center;
	-ms-justify-content: center;
	-webkit-justify-content: center;
}

.cbp-pggrid > li {
	display: inline-block;
	vertical-align: top;
	position: relative;
	width: 33%;
	min-width: 340px;
	max-width: 555px;
	padding: 20px 2% 50px 2%;
	text-align: left;
}

.flexbox .cbp-pggrid > li {
	display: block;
}

.cbp-pgcontent {
	border: 3px solid #47a3da;
	position: relative;
}

.cbp-pgrotate {
	width: 36px;
	height: 36px;
	position: absolute;
	display: block;
	color: transparent;
	font-size: 0;
	z-index: 100;
	border-bottom: 3px solid #47a3da;
	border-left: 3px solid #47a3da;
	right: 0px;
	top: 0px;
	cursor: pointer;
	text-align: center;
}

.cbp-pgrotate:before {
	font-size: 18px;
	line-height: 32px;
	color: #47a3da;
}

.no-touch .cbp-pgrotate:hover,
.cbp-pgrotate.cbp-pgrotate-active {
	background: #47a3da;
}

.no-touch .cbp-pgrotate:hover:before,
.cbp-pgrotate.cbp-pgrotate-active:before {
	color: #fff;
}

/* The item with the images will have perspective */
.cbp-pgitem {
	width: 100%;
	position: relative;
	padding: 2em;
	-webkit-perspective: 1400px;
	-moz-perspective: 1000px;
	perspective: 1000px;
	-webkit-backface-visibility: hidden;
	-moz-backface-visibility: hidden;
	backface-visibility: hidden;
}

/* The flip container */
.cbp-pgitem-flip {
	-webkit-transform-style: preserve-3d;
	-moz-transform-style: preserve-3d;
	transform-style: preserve-3d;
	-webkit-transition: -webkit-transform .4s ease-out;
	-moz-transition: -moz-transform .4s ease-out;
	transition: transform .4s ease-out;
}

/* If you want to rotate on hover instead of click, you could use something like this: 
.cbp-pgrotate:hover + .cbp-pgitem .cbp-pgitem-flip
*/
.cbp-pgitem.cbp-pgitem-showback .cbp-pgitem-flip {
	-webkit-transform: rotateY(180deg);
	-moz-transform: rotateY(180deg);
	transform: rotateY(180deg);
}

.cbp-pgitem-flip img {
	display: block;
	margin: 0 auto;
	max-width: 100%;
	max-height: 100%;
	-webkit-backface-visibility: hidden;
	-moz-backface-visibility: hidden;
	backface-visibility: hidden;
}

.cbp-pgitem img:first-child {
	position: relative;
}

/* The second image will be rotated so that we'd be looking at the back of it */
.cbp-pgitem img:nth-child(2) {
	position: absolute;
	top: 50%;
	left: 50%;
	-webkit-transform: translateX(-50%) translateY(-50%) rotateY(-180deg);
	-moz-transform: translateX(-50%) translateY(-50%) rotateY(-180deg);
	transform: translateX(-50%) translateY(-50%) rotateY(-180deg);
}

/* Fallback for browsers that don't support 3d transforms */
.no-csstransforms3d .cbp-pgitem img:nth-child(2) {
	position: relative;
	top: 0;
	left: 0;
	display: none;
}

.no-csstransforms3d .cbp-pgitem.cbp-pgitem-showback img:first-child {
	display: none;
}

.no-csstransforms3d .cbp-pgitem.cbp-pgitem-showback img:nth-child(2) {
	display: block;
}

/* The options bar */
.cbp-pgoptions {
	height: 60px;
	width: 100%;
 	border-top: 3px solid #47a3da;;
}

.cbp-pgoptions > li {
	width: 20%;
	height: 100%;
	float: left;
	position: relative;
	display: block;
	cursor: pointer;
	color: transparent;
	font-size: 0;
	border-left: 3px solid #47a3da;
	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

.cbp-pgoptions > li:first-child {
	border-left: none;
} 

.no-touch .cbp-pgoptions li {
	color: #47a3da;
}

.no-touch .cbp-pgoptions li:hover,
.cbp-pgoptions li.cbp-pgoption-active {
	background: #47a3da;
}

.cbp-pgoptions li:before,
.cbp-pgoptions li > span {
	font-size: 22px;
	line-height: 60px;
	text-indent: 0;
	text-align: center;
	color: #47a3da;
}

.no-touch .cbp-pgoptions li:hover:before,
.no-touch .cbp-pgoptions li:hover > span,
.cbp-pgoptions li.cbp-pgoption-active > span  {
	color: #fff;
}

.cbp-pgoptions li.cbp-pgoptsize > span {
	font-size: 22px;
}

.cbp-pgoptions li > span {
	display: block;
}

.cbp-pgoptions li:before {
	position: absolute;
	width: 100%;
	height: 100%;
}

/* Icons */
.cbp-pgoptcompare, 
.cbp-pgoptcart, 
.cbp-pgoptfav,
.cbp-pgrotate {
	font-family: 'pgicons';
	speak: none;
	font-style: normal;
	font-weight: normal;
	font-variant: normal;
	text-transform: none;
	line-height: 1;
	-webkit-font-smoothing: antialiased;
}

.cbp-pgoptcompare:before {
	content: "\e001";
}

.cbp-pgoptfav:before {
	content: "\e003";
}

.cbp-pgoptfav.cbp-pgoptfav-selected:before {
	content: "\e002";
	color: #ee73b8;
}

.cbp-pgoptfav.cbp-pgoptfav-selected:hover:before {
	color: #f9c0e0;
}

.cbp-pgoptcart:before {
	content: "\e000";
}

.cbp-pgrotate:before {
	content: "\e004";
}

/* Tooltips */
.cbp-pgopttooltip {
	position: absolute;
	bottom: 180%;
	margin-bottom: 0px;
	background: #fff;
	padding: 25px;
	width: 100px;
	left: 50%;
	margin-left: -50px;
	border: 3px solid #47a3da;
	opacity: 0;
	z-index: 1000;
	visibility: hidden;
	pointer-events: none;
	-webkit-transition: visibility 0s 0.3s, opacity 0.3s, bottom 0.3s;
	-moz-transition: visibility 0s 0.3s, opacity 0.3s, bottom 0.3s;
	transition: visibility 0s 0.3s, opacity 0.3s, bottom 0.3s;
}

.cbp-pgoptions li:hover .cbp-pgopttooltip,
.cbp-pgoptions li.cbp-pgoption-active .cbp-pgopttooltip {
	visibility: visible;
	opacity: 1;
	-webkit-transition-delay: 0s;
	-moz-transition-delay: 0s;
	transition-delay: 0s;
	bottom: 100%;
	pointer-events: auto;
}

.cbp-pgopttooltip:after,
.cbp-pgopttooltip:before {
	top: 100%;
	border: solid transparent;
	content: " ";
	height: 0;
	width: 0;
	position: absolute;
	pointer-events: none;
}

.cbp-pgopttooltip:after {
	border-color: transparent;
	border-top-color: #fff;
	border-width: 10px;
	left: 50%;
	margin-left: -10px;
}

.cbp-pgopttooltip:before {
	border-color: transparent;
	border-top-color: #47a3da;
	border-width: 14px;
	left: 50%;
	margin-left: -14px;
}

/* Size tooltip */
.cbp-pgoptsize .cbp-pgopttooltip {
	margin-left: -50px;
}

.cbp-pgoptsize .cbp-pgopttooltip span {
	display: block;
	text-indent: 0;
	background: url(../images/tshirt.svg) no-repeat center center;
	background-size: 100%;
	margin: 0 auto 4px;
	text-align: center;
	font-size: 12px;
	font-weight: 700;
	color: #65b3e2;
}

.cbp-pgoptsize .cbp-pgopttooltip span:hover {
	color: #0968a1;
	-webkit-transform: scale(1.1);
	-moz-transform: scale(1.1);
	transform: scale(1.1);
}

.cbp-pgoptsize .cbp-pgopttooltip span[data-size="XL"] {
	width: 44px;
	height: 44px;
	line-height: 44px;
}

.cbp-pgoptsize .cbp-pgopttooltip span[data-size="L"] {
	width: 40px;
	height: 40px;
	line-height: 40px;
}

.cbp-pgoptsize .cbp-pgopttooltip span[data-size="M"] {
	width: 34px;
	height: 34px;
	line-height: 34px;
}

.cbp-pgoptsize .cbp-pgopttooltip span[data-size="S"] {
	width: 30px;
	height: 30px;
	line-height: 30px;
}

/* Color tooltip */
.cbp-pgoptcolor .cbp-pgopttooltip {
	padding: 5px;
}

.cbp-pgoptions li.cbp-pgoptcolor > span,
.cbp-pgoptcolor .cbp-pgopttooltip span {
	display: block;
	margin: 12px auto 0;
	text-indent: -900em;
}

.cbp-pgoptions li.cbp-pgoptcolor > span {
	width: 36px;
	height: 36px;
	border: 3px solid #fff;
}

.cbp-pgoptcolor .cbp-pgopttooltip span {
	float: left;
	margin: 4px;
	width: 34px;
	height: 34px;
}

.no-touch .cbp-pgoptcolor .cbp-pgopttooltip span:hover {
	-webkit-transform: scale(1.1);
	-moz-transform: scale(1.1);
	transform: scale(1.1);
}

.cbp-pgoptcolor span[data-color="c1"] {
	background: #72bbe9;
}

.cbp-pgoptcolor span[data-color="c2"] {
	background: #e577aa;
}

.cbp-pgoptcolor span[data-color="c3"] {
	background: #e5b178;
}

.cbp-pgoptcolor span[data-color="c4"] {
	background: #7abe93;
}

.cbp-pginfo {
	padding-top: 10px;
}

.cbp-pginfo:before,
.cbp-pginfo:after {
	content: " ";
	display: table;
}

.cbp-pginfo:after {
	clear: both;
}

.cbp-pginfo h3,
.cbp-pginfo span {
	float: left;
	width: 50%;
	font-size: 1.8em;
	padding: 10px 5px;
	margin: 0;
}

.cbp-pginfo h3 {
	font-weight: 300;
}

.cbp-pginfo span {
	font-weight: 700;
	text-align: right;
}
/* Media Queries */

@media screen and (max-width: 68.125em) {
	.cbp-pggrid > li {
		width: 48%;
	}
}

@media screen and (max-width: 46.125em) {
	.cbp-pggrid > li {
		width: 100%;
	}
}

The JavaScript

/**
 * cbpShop.js v1.0.0
 * http://www.codrops.com
 *
 * Licensed under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 * 
 * Copyright 2013, Codrops
 * http://www.codrops.com
 */
;( function( window ) {
	
	'use strict';

	function cbpShop( el ) {
		this.el = el;
		this._init();
	}

	cbpShop.prototype = {
		_init : function() {
			var self = this;
			
			this.touch = Modernizr.touch;

			this.products = this.el.querySelectorAll( 'ul.cbp-pggrid > li' );
			Array.prototype.slice.call( this.products ).forEach( function( el, i ) {
				var content = el.querySelector( 'div.cbp-pgcontent' ),
					item = content.querySelector( 'div.cbp-pgitem' ),
					rotate = content.querySelector( 'span.cbp-pgrotate' );

				if( self.touch ) {

					rotate.addEventListener( 'touchstart', function() { self._rotateItem( this, item ); } );

					var options = content.querySelector( 'ul.cbp-pgoptions' ),
						size = options.querySelector( 'li.cbp-pgoptsize > span' ),
						color = options.querySelector( 'li.cbp-pgoptcolor > span' );
					
					size.addEventListener( 'touchstart', function() { self._showItemOptions( this ); } );
					color.addEventListener( 'touchstart', function() { self._showItemOptions( this ); } );
				}
				else {
					rotate.addEventListener( 'click', function() { self._rotateItem( this, item ); } );
				}
			} );
		},
		_rotateItem : function( trigger, item ) {
			if( item.getAttribute( 'data-open' ) === 'open' ) {
				item.setAttribute( 'data-open', '' );
				trigger.className = trigger.className.replace(/\b cbp-pgrotate-active\b/,'');
				item.className = item.className.replace(/\b cbp-pgitem-showback\b/,'');
			}
			else {
				item.setAttribute( 'data-open', 'open' );
				trigger.className += ' cbp-pgrotate-active';
				item.className += ' cbp-pgitem-showback';
			}
		},
		_showItemOptions : function( trigger ) {
			if( trigger.getAttribute( 'data-open' ) === 'open' ) {
				trigger.setAttribute( 'data-open', '' );
				trigger.parentNode.className = trigger.parentNode.className.replace(/\b cbp-pgoption-active\b/,'');
			}
			else {
				trigger.setAttribute( 'data-open', 'open' );
				trigger.parentNode.className += ' cbp-pgoption-active';
			}
		},
		/*
		other functions..
		*/
	}

	window.cbpShop = cbpShop;

} )( window );

View demo Download source

Previous:
Next:

Tagged with:

Mary Lou (Manoela Ilic) is a freelance web designer and developer with a passion for interaction design. She studied Cognitive Science and Computational Logic and has a weakness for the smell of freshly ground peppercorns.

View all contributions by

Website: http://tympanus.net/

Related Articles

Feedback 18

Comments are closed.
  1. 2

    Your designs are absolutely wonderful, thank you so much for sharing!

  2. 6

    wow.. its really nice and simple. love it… thank you for sharing..

  3. 8

    Hi,

    I got javascript object error (Jscript object is expected on line 11) on IE8 (windows 7 starter)
    Error details (sorry in dutch) are below

    Foutdetails webpagina

    Gebruikersagent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; Tablet PC 2.0)
    Tijdstempel: Sat, 18 May 2013 23:07:02 UTC

    Bericht: JScript-object wordt verwacht
    Regel: 11
    Teken: 180
    Code: 0
    URI: http://tympanus.net/Blueprints/ProductGridLayout/js/cbpShop.min.js

  4. 14

    I’m having problem with the demo, the tooltip perfectly shown up when i hover it, but when i click any button (the rotate and the others) nothing happen. I’m using Chrome Version 27.0.1453.93 m , on Windows 8. Thanks by the way, and absolutely great work!

  5. 16

    Love the template – really useful and great design. I’m struggling to turn the cart and heart icons into a link – any pointers? I’d like the user to be able to click on the icons as a link. Thanks!

  6. 18

    This used to work on FF a while back but doesn’t anymore. Reason is that you mordernizr detect’s ff’s touch feature and hence the script runs the code to listen to touch events instead of click events.

    So what i did was i did a little digging to find an html5 property which gets detected by FF but not on mobile devices and i found flexbox to be one of them. (Read: http://html5please.com/#flexbox). And i wrote some script to have the problem fixed. Replace the following code with the line # 32 to 45 in cbpShop.js to have your problem fixed:


    // Detecting Browser Touch events
    if( self.touch) {

    // Temp Mozilla FF Fix for Touch Events
    if ( jQuery('html').hasClass('flexbox') ) {
    rotate.addEventListener( 'click', function() { self._rotateItem( this, item ); } );
    }

    // Touch Devices
    else {
    rotate.addEventListener( 'touchstart', function() { self._rotateItem( this, item ); } );

    var options = content.querySelector( 'ul.cbp-pgoptions' ),
    size = options.querySelector( 'li.cbp-pgoptsize > span' ),
    color = options.querySelector( 'li.cbp-pgoptcolor > span' );

    size.addEventListener( 'touchstart', function() { self._showItemOptions( this ); } );
    color.addEventListener( 'touchstart', function() { self._showItemOptions( this ); } );
    }
    }
    else {
    rotate.addEventListener( 'click', function() { self._rotateItem( this, item ); } );
    }

    Please note I’m using jQuery to detect flexbox class on html element, I’m sure there’s a better way by using mordernizr’s api. Also this is a temporary fix until mobile devices start supporting flexbox. But i’m sure mordernizr would update soon to have this fixed.

Comments are closed.