From our sponsor: Ready to show your plugin skills? Enter the Penpot Plugins Contest (Nov 15-Dec 15) to win cash prizes!
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%; } }
Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.
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-activeb/,''); item.className = item.className.replace(/b cbp-pgitem-showbackb/,''); } 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-activeb/,''); } else { trigger.setAttribute( 'data-open', 'open' ); trigger.parentNode.className += ' cbp-pgoption-active'; } }, /* other functions.. */ } window.cbpShop = cbpShop; } )( window );
Love the cute little t-shirt sizes in the sizes tooltip 😀
Your designs are absolutely wonderful, thank you so much for sharing!
Very nice demo. Everyone would like to implement, easy and fast.
i think so (y)
This is so cool I could cry. In fact, I must excuse myself.
wow.. its really nice and simple. love it… thank you for sharing..
Where disapeared your search box?
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
Once again, Super Work. Thumbs up.
Doesn’t work with IE10
In Google Chrome, when I rotate the shirt, it’s out of the box.
May I ask which version of Chrome you are using?
Hi, i’m using version 23.0.1271.97 on Ubuntu
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!
wonderfull design., thank for sharer
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!
Awesome. I’m making a T-shirt site. This is perfect. Thanks!
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.
Hello, I want to point out that the link Demo leads to a 404 page.
Thanks a lot, Salvatore! It’s fixed now 🙂 Cheers, ML
what if there’s like uneven numbers such as 5 products, the product at the bottom by itself is in the center instead of left. So I added float: left, and it works, but I noticed that the products are slightly to the left instead of being centered. Is there anyway I can fix it?
Thank you so much!