Product Comparison Layout & Effect

A basic responsive product grid layout with comparison functionality and a slide-in effect.

This Blueprint is a responsive product grid layout with comparison functionality. A maximum of three items can be selected for the product comparison. The comparison view shows flexbox-powered columns or rows (depending on the viewport size) that appear with a slide-in effect. There are a couple of example media queries for smaller viewports.

Browser Support:
  • ChromeSupported
  • FirefoxSupported
  • Internet ExplorerSupported from version 10+
  • SafariSupported
  • OperaSupported

The HTML

<!-- Compare basket -->
<div class="compare-basket">
	<!-- comparison item miniatures come here -->
	<button class="action action--button action--compare"><i class="fa fa-check"></i><span class="action__text">Compare</span></button>
</div>
<!-- Main view -->
<div class="view">
	<!-- Blueprint header -->
	<header class="bp-header cf"><!-- ... --></header>
	<!-- Product grid -->
	<section class="grid">
		<!-- Products -->
		<div class="product">
			<div class="product__info">
				<img class="product__image" src="images/1.png" alt="Product 1" />
				<h3 class="product__title">Chryseia</h3>
				<span class="product__year extra highlight">2011</span>
				<span class="product__region extra highlight">Douro</span>
				<span class="product__varietal extra highlight">Touriga Nacional</span>
				<span class="product__alcohol extra highlight">13%</span>
				<span class="product__price highlight">$55.90</span>
				<button class="action action--button action--buy">
					<i class="fa fa-shopping-cart"></i>
					<span class="action__text">Add to cart</span>
				</button>
			</div>
			<label class="action action--compare-add">
				<input class="check-hidden" type="checkbox" />
				<i class="fa fa-plus"></i>
				<i class="fa fa-check"></i>
				<span class="action__text action__text--invisible">Add to compare</span>
			</label>
		</div>
		<div class="product">
			<!-- ... -->
		</div>
		<div class="product">
			<!-- ... -->		
		</div>
		<!-- ... -->	
		</section>
</div><!-- /view -->
<!-- product compare wrapper -->
<section class="compare">
	<!-- comparison items come here -->
	<button class="action action--close"><i class="fa fa-remove"></i><span class="action__text action__text--invisible">Close comparison overlay</span></button>
</section>
<script src="js/classie.js"></script>
<script src="js/main.js"></script>

The CSS

.view {
	-webkit-transition: -webkit-transform 0.4s ease-in-out;
	transition: transform 0.4s ease-in-out;
}

.view--compare {
	-webkit-transform: scale3d(0.9,0.9,1);
	transform: scale3d(0.9,0.9,1);
}

/* product grid */
.grid {
	margin: 0 auto;
	padding: 4em 1em;
	max-width: 1200px;
	text-align: center;
	overflow: hidden;
	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

/* if flexbox is supported, let's use it to lay out the products */
.flexbox .grid {
	display: -ms-flexbox;
	display: -webkit-flex;
	display: flex;
	-webkit-flex-direction: row;
	-ms-flex-direction: row;
	flex-direction: row;
	-webkit-flex-wrap: wrap;
	-ms-flex-wrap: wrap;
	flex-wrap: wrap;
	-webkit-justify-content: center;
	-ms-flex-pack: center;
	justify-content: center;
	-webkit-align-content: stretch;
	-ms-flex-line-pack: stretch;
	align-content: stretch;
	-webkit-align-items: flex-start;
	-ms-flex-align: start;
	align-items: flex-start;
}

/* product */
.product {
	position: relative;
	display: inline-block;
	vertical-align: top;
	min-width: 16em;
	margin: 0 1em 2.5em;
	padding: 1.5em 1.5em 2em;
	background: #24252A;
	border-radius: 5px;
}

.product--selected {
	box-shadow: 0 0 0 2px #5C5EDC;
}

.flexbox .product {
	display: block;
	-webkit-flex: 0 0 16em;
	-ms-flex: 0 0 16em;
	flex: 0 0 16em;
}

/* product info */
.product__info > span {
	display: block;
	padding: 1em 0;
}

/* since we'll be using the product info inside of the comparison, we'll hide the extra info for the grid view */
.grid .extra {
	display: none;
}

.product__image {
	display: block;
	margin: 0 auto;
	max-width: 100%;
	-webkit-transform-origin: 50% 100%;
	transform-origin: 50% 100%;
}

.product:hover .product__image {
	-webkit-animation: swing 0.6s forwards;	
	animation: swing 0.6s forwards;	
}

/* https://daneden.github.io/animate.css/ */
@-webkit-keyframes swing {
	25% {
		-webkit-transform: rotate3d(0, 0, 1, 6deg);
		transform: rotate3d(0, 0, 1, 6deg);
	}

	50% {
		-webkit-transform: rotate3d(0, 0, 1, -4deg);
		transform: rotate3d(0, 0, 1, -4deg);
	}

	75% {
		-webkit-transform: rotate3d(0, 0, 1, 2deg);
		transform: rotate3d(0, 0, 1, 2deg);
	}

	100% {
		-webkit-transform: rotate3d(0, 0, 1, 0deg);
		transform: rotate3d(0, 0, 1, 0deg);
	}
}

@keyframes swing {
  25% {
		-webkit-transform: rotate3d(0, 0, 1, 6deg);
		transform: rotate3d(0, 0, 1, 6deg);
	}

	50% {
		-webkit-transform: rotate3d(0, 0, 1, -4deg);
		transform: rotate3d(0, 0, 1, -4deg);
	}

	75% {
		-webkit-transform: rotate3d(0, 0, 1, 2deg);
		transform: rotate3d(0, 0, 1, 2deg);
	}

	100% {
		-webkit-transform: rotate3d(0, 0, 1, 0deg);
		transform: rotate3d(0, 0, 1, 0deg);
	}
}

.product__title {
	font-size: 150%;
	margin: 1em 0 0;
	min-height: 3em;
}

.product__price {
	font-weight: bold;
	color: #797BED;
}

.action {
	display: inline-block;
	font-size: 1em;
	white-space: nowrap;
	padding: 0.85em 1.25em;
	cursor: pointer;
	border: none;
	background: transparent;
	text-align: center;
}

.action:focus {
	outline: none;
}

.action--button {
	background: #2C2D34;
	color: #fff;
	border-radius: 2px;
	-webkit-transition: background 0.2s;
	transition: background 0.2s;
}

.action--button:hover {
	background: #5C5EDC;
}

.action__text {
	font-family: 'Raleway', 'Helvetica Neue', Helvetica, Arial, sans-serif;
	font-weight: bold;
	letter-spacing: 1px;
	font-size: .813em;
	vertical-align: middle;
	display: inline-block;
}

.action__text--invisible {
	position: absolute;
	top: 100%;
	opacity: 0;
	pointer-events: none;
}

.action--button i + span {
	margin-left: 1em;
}

.flexbox .action--buy {
	-webkit-align-self: center;
	-ms-flex-item-align: center;
	align-self: center;
	margin-top: 1em;
	-webkit-flex: none;
	-ms-flex: none;
	flex: none;
}

.action--close {
	position: absolute;
	overflow: hidden;
	top: 0;
	right: 0;
	font-size: 1.5em;
	color: #ddd;
	pointer-events: none;
	opacity: 0;
	-webkit-transition: opacity 0.3s, background 0.2s;
	transition: opacity 0.3s, background 0.2s;
}

.view--compare + .compare .action--close {
	pointer-events: auto;
	opacity: 1;
	-webkit-transition-delay: 0.4s, 0s;
	transition-delay: 0.4s, 0s;
}

.action--close:hover,
.action--close:focus {
	color: #797BED;
}

.action--compare {
	margin: 0 0 0 4px;
	opacity: 0;
	pointer-events: none;
	cursor: default;
	background-color: #34363D;
	color: #565B6C;
	-webkit-transition: opacity 0.3s;
	transition: opacity 0.3s;
}

.compare-basket--active .action--compare {
	opacity: 1;
}

.compare-basket--active .action--compare:nth-child(3),
.compare-basket--active .action--compare:nth-child(4) {
	background-color: #494BC7;
	color: #fff;
	pointer-events: auto;
	cursor: pointer;
}

.action--remove {
	position: absolute;
	overflow: hidden;
	color: #ddd;
	top: 0px;
	right: 2px;
	padding: 0;
	font-size: 0.65em;
}

.action--compare-add {
	color: #ddd;
	position: absolute;
	top: 10px;
	right: 5px;
}

.action--compare-add:hover .action__text--invisible {
	opacity: 1;
	top: 35px;
	right: 10px;
	color: #ddd;
	font-size: 75%;
	letter-spacing: 0;
	background: #2F3035;
	border-radius: 2px;
	padding: 3px 5px;
}

.action--remove:hover,
.action--compare-add:hover {
	color: #5C5EDC;
}

.action--compare-add .fa-check,
.action--compare-add input[type=checkbox]:checked ~ .fa-plus {
	display: none;
}

.action--compare-add input[type=checkbox]:checked ~ .fa-check {
	display: block;
	color: #5C5EDC;
}

.check-hidden {
	position: absolute;
	opacity: 0;
}

.compare-basket {
	width: 100%;
	padding: 0.75em;
	text-align: right;
	position: fixed;
	top: 0;
	left: 0;
	background: #212227;
	z-index: 1000;
	-webkit-touch-callout: none;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
	-webkit-transform: translate3d(0,-100%,0);
	transform: translate3d(0,-100%,0);
	-webkit-transition: -webkit-transform 0.3s cubic-bezier(0.2,1,0.3,1);
	transition: transform 0.3s cubic-bezier(0.2,1,0.3,1);
}

.compare-basket--active {
	-webkit-transform: translate3d(0,0,0);
	transform: translate3d(0,0,0);
}

.flexbox .compare-basket {
	display: -webkit-flex;
	display: -ms-flex;
	display: flex;
	-webkit-justify-content: flex-end;
	-ms-flex-pack: end;
	justify-content: flex-end;
}

.product-icon {
	display: inline-block;
	vertical-align: middle;
	background: #42444F;
	width: 50px;
	height: 50px;
	padding: 5px;
	margin: 0 3px;
	border-radius: 2px;
	position: relative;
}

.product-icon::after {
	content: '';
	position: absolute;
	width: 100%;
	height: 100%;
	top: 0;
	left: 0;
	border-radius: 4px;
	z-index: -1;
	box-shadow: -56px 0 #2C2D34;
}

.compare-basket--full .product-icon::after {
	display: none;
}

.flexbox .product-icon {
	display: block;
}

/* comparison overlay */
.compare {
	position: fixed;
	z-index: 100;
	width: 100%;
	height: 0;
	overflow: hidden;
	top: 0;
	left: 0;
	z-index: 1001;
	-webkit-transition: height 0s 0.4s;
	transition: height 0s 0.4s;
}

.flexbox .compare {
	display: -webkit-flex;
	display: -ms-flex;
	display: flex;
}

.view--compare + .compare {
	pointer-events: auto;
	height: 100%;
	-webkit-transition: none;
	transition: none;
}

.compare::before {
	content: '';
	position: absolute;
	width: 100%;
	height: 100%;
	top: 0;
	left: 0;
	background: rgba(0,0,0,0.5);
	opacity: 0;
	-webkit-transition: opacity 0.4s;
	transition: opacity 0.4s;
}

.view--compare + .compare::before {
	opacity: 1;
}

.compare__item {
	height: 100%;
	width: 50%;
	background: #24252A;
	text-align: center;
	cursor: default;
	padding: 2em 0;
	-webkit-transition: -webkit-transform 0.4s ease-in-out;
	transition: transform 0.4s ease-in-out;
}

.no-flexbox .compare__item {
	display: inline-block;
	width: 50%;
}

.compare__item:nth-of-type(2) {
	background: #212227;
}

.compare__item .product__title {
	margin: 1em 0;
	min-height: 0;
}

.compare__item .product__price {
	color: #CECECE;
}

.compare__item span[class^="product__"] {
	display: block;
	padding: 0.85em 0;
	-webkit-transition: background-color 0.3s;
	transition: background-color 0.3s;
}

.compare__item span[class^="product__"].hover {
	background: #000;
}

/* three items */
.compare__item:first-of-type:nth-last-of-type(3),
.compare__item:first-of-type:nth-last-of-type(3) ~ .compare__item {
    width: 33.3333%;
}

.flexbox .compare__item {
	-webkit-flex: auto;
	-ms-flex: auto;
	flex: auto;
}

.compare__item:nth-child(odd) {
	-webkit-transform: translate3d(0,-100vh,0);
	transform: translate3d(0,-100vh,0);
}

.compare__item:nth-child(even) {
	-webkit-transform: translate3d(0,100vh,0);
	transform: translate3d(0,100vh,0);
}

.view--compare + .compare .compare__item:nth-child(odd),
.view--compare + .compare .compare__item:nth-child(even) {
	-webkit-transform: translate3d(0,0,0);
	transform: translate3d(0,0,0);
}

.compare__effect {
	width: 100%;
	height: 100%;
	opacity: 0;
	-webkit-transition: -webkit-transform 1s cubic-bezier(0.2, 1, 0.3, 1), opacity 1s cubic-bezier(0.2, 1, 0.3, 1);
	transition: transform 1s cubic-bezier(0.2, 1, 0.3, 1), opacity 1s cubic-bezier(0.2, 1, 0.3, 1);
}

.compare__item:nth-child(odd) .compare__effect {
	-webkit-transform: translate3d(0,-250px,0);
	transform: translate3d(0,-250px,0);
}

.compare__item:nth-child(even) .compare__effect {
	-webkit-transform: translate3d(0,250px,0);
	transform: translate3d(0,250px,0);
}

.view--compare + .compare .compare__item:nth-child(odd) .compare__effect,
.view--compare + .compare .compare__item:nth-child(even) .compare__effect {
	opacity: 1;
	-webkit-transform: translate3d(0,0,0);
	transform: translate3d(0,0,0);
	-webkit-transition-delay: 0.3s;
	transition-delay: 0.3s;
}

@media screen and (min-width: 59.688em) {
	.flexbox .compare__effect {
		display: -webkit-flex;
		display: -ms-flex;
		display: flex;
		-webkit-flex-direction: column;
		-ms-flex-direction: column;
		flex-direction: column;
		-webkit-justify-content: space-between;
		-ms-flex-pack: justify;
		justify-content: space-between;
	}

	.flexbox .compare__item .product__image {
		-webkit-align-self: center;
		-ms-flex-align: center;
		align-self: center;
		-webkit-flex: none;
		-ms-flex: none;
		flex: none;
	}
}

@media screen and (max-width: 59.688em) {
	.grid {
		padding: 2em 0.5em;
		font-size: 65%;
	}

	.product {
		margin: 0 0.5em 1em;
		min-width: 13em;
	}

	.product__title {
		font-size: 115%;
	}

	.flexbox .product {
		-webkit-flex: 0 0 13em;
		-ms-flex: 0 0 13em;
		flex: 0 0 13em;
	}

	.flexbox .compare {
		-webkit-flex-direction: column;
		-ms-flex-direction: column;
		flex-direction: column;
	}

	.no-flexbox .compare__item,
	.compare__item,
	.compare__item:first-of-type:nth-last-of-type(3),
	.compare__item:first-of-type:nth-last-of-type(3) ~ .compare__item {
		width: 100%;
	}

	.compare__item {
		text-align: left;
		padding: 1.5em;
		font-size: 90%;
	}

	.compare__item .product__image {
		height: 40px;
		float: left;
	}

	.compare__item .product__title {
		margin: 0 40px 0 43px;
		font-size: 0.85em;
		display: block;
	}

	.compare__item .product__year { border-bottom: 2px solid #2E294E; }
	.compare__item .product__region { border-bottom: 2px solid #6D6FD2; }
	.compare__item .product__varietal { border-bottom: 2px solid #4B5267; }
	.compare__item .product__alcohol { border-bottom: 2px solid #3C3474; }

	.action--close {
		padding: 0.5em 0.75em;
	}

	.compare__item .action--buy {
		margin: 0;
		display: block;
	}

	.compare__item span[class^="product__"] {
		display: inline-block;
		padding: 0.25em;
		margin: 0 0 0.5em 0;
		font-size: 0.85em;
	}

	.compare__item:nth-child(odd) {
		-webkit-transform: translate3d(-100%,0,0);
		transform: translate3d(-100%,0,0);
	}

	.compare__item:nth-child(even) {
		-webkit-transform: translate3d(100%,0,0);
		transform: translate3d(100%,0,0);
	}

	.compare__item:nth-child(odd) .compare__effect {
		-webkit-transform: translate3d(-250px,0,0);
		transform: translate3d(-250px,0,0);
	}

	.compare__item:nth-child(even) .compare__effect {
		-webkit-transform: translate3d(250px,0,0);
		transform: translate3d(250px,0,0);
	}
}

The JavaScript

/**
 * main.js
 * http://www.codrops.com
 *
 * Licensed under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 * 
 * Copyright 2015, Codrops
 * http://www.codrops.com
 */
(function() {

	var viewEl = document.querySelector('.view'),
		gridEl = viewEl.querySelector('.grid'),
		items = [].slice.call(gridEl.querySelectorAll('.product')),
		basket;

	// the compare basket
	function CompareBasket() {
		this.el = document.querySelector('.compare-basket');
		this.compareCtrl = this.el.querySelector('.action--compare');
		this.compareWrapper = document.querySelector('.compare'),
		this.closeCompareCtrl = this.compareWrapper.querySelector('.action--close')
		
		this.itemsAllowed = 3;
		this.totalItems = 0;
		this.items = [];

		// compares items in the compare basket: opens the compare products wrapper
		this.compareCtrl.addEventListener('click', this._compareItems.bind(this));
		// close the compare products wrapper
		var self = this;
		this.closeCompareCtrl.addEventListener('click', function() {
			// toggle compare basket
			classie.add(self.el, 'compare-basket--active');
			// animate..
			classie.remove(viewEl, 'view--compare');
		});
	}

	CompareBasket.prototype.add = function(item) {
		// check limit
		if( this.isFull() ) {
			return false;
		}

		classie.add(item, 'product--selected');

		// create item preview element
		var preview = this._createItemPreview(item);
		// prepend it to the basket
		this.el.insertBefore(preview, this.el.childNodes[0]);
		// insert item
		this.items.push(preview);

		this.totalItems++;
		if( this.isFull() ) {
			classie.add(this.el, 'compare-basket--full');
		}

		classie.add(this.el, 'compare-basket--active');
	};

	CompareBasket.prototype._createItemPreview = function(item) {
		var self = this;

		var preview = document.createElement('div');
		preview.className = 'product-icon';
		preview.setAttribute('data-idx', items.indexOf(item));
		
		var removeCtrl = document.createElement('button');
		removeCtrl.className = 'action action--remove';
		removeCtrl.innerHTML = '<i class="fa fa-remove"></i><span class="action__text action__text--invisible">Remove product</span>';
		removeCtrl.addEventListener('click', function() {
			self.remove(item);
		});
		
		var productImageEl = item.querySelector('img.product__image').cloneNode(true);

		preview.appendChild(productImageEl);
		preview.appendChild(removeCtrl);

		var productInfo = item.querySelector('.product__info').innerHTML;
		preview.setAttribute('data-info', productInfo);

		return preview;
	};

	CompareBasket.prototype.remove = function(item) {
		classie.remove(this.el, 'compare-basket--full');
		classie.remove(item, 'product--selected');
		var preview = this.el.querySelector('[data-idx = "' + items.indexOf(item) + '"]');
		this.el.removeChild(preview);
		this.totalItems--;

		var indexRemove = this.items.indexOf(preview);
		this.items.splice(indexRemove, 1);

		if( this.totalItems === 0 ) {
			classie.remove(this.el, 'compare-basket--active');
		}

		// checkbox
		var checkbox = item.querySelector('.action--compare-add > input[type = "checkbox"]');
		if( checkbox.checked ) {
			checkbox.checked = false;
		}
	};

	CompareBasket.prototype._compareItems = function() {
		var self = this;

		// remove all previous items inside the compareWrapper element
		[].slice.call(this.compareWrapper.querySelectorAll('div.compare__item')).forEach(function(item) {
			self.compareWrapper.removeChild(item);
		});

		for(var i = 0; i < this.totalItems; ++i) {
			var compareItemWrapper = document.createElement('div');
			compareItemWrapper.className = 'compare__item';

			var compareItemEffectEl = document.createElement('div');
			compareItemEffectEl.className = 'compare__effect';

			compareItemEffectEl.innerHTML = this.items[i].getAttribute('data-info');
			compareItemWrapper.appendChild(compareItemEffectEl);

			this.compareWrapper.insertBefore(compareItemWrapper, this.compareWrapper.childNodes[0]);
		}

		setTimeout(function() {
			// toggle compare basket
			classie.remove(self.el, 'compare-basket--active');
			// animate..
			classie.add(viewEl, 'view--compare');
		}, 25);
	};

	CompareBasket.prototype.isFull = function() {
		return this.totalItems === this.itemsAllowed;
	};

	function init() {
		// initialize an empty basket
		basket = new CompareBasket();
		initEvents();
	}

	function initEvents() {
		items.forEach(function(item) {
			var checkbox = item.querySelector('.action--compare-add > input[type = "checkbox"]');
			checkbox.checked = false;

			// ctrl to add to the "compare basket"
			checkbox.addEventListener('click', function(ev) {
				if( ev.target.checked ) {
					if( basket.isFull() ) {
						ev.preventDefault();
						return false;
					}
					basket.add(item);
				}
				else {
					basket.remove(item);
				}
			});
		});
	}

	init();

})();

Tagged with:

Manoela Ilic

Manoela is the main tinkerer at Codrops. With a background in coding and passion for all things design, she creates web experiments and keeps frontend professionals informed about the latest trends.

Stay up to date with the latest web design and development news and relevant updates from Codrops.

Feedback 34

Comments are closed.
  1. The comparison bar doesn’t show up in Opera/Chrome. I’m guessing it has to do with this error that pops up in the console: Uncaught SyntaxError: Failed to execute 'querySelector' on 'Element': '.action--compare-add > input[type = "checkbox"]' is not a valid selector.

    In Firefox, it works fine and looks beautiful!

    • Works fine in Chrome 45 (Canary) but not in Chrome 43 (latest stable).
      Same error as Sander mentions.

      Looks fantastic in Chrome Canary though, nice job.

  2. Only issue I see here is that I can scroll while being in the comparison view. Some overflow: hidden would be great here.

  3. Really great demo, love some of the examples you put up on this site. Looking forward to implementing it in a new design I’m working on and seeing if it fits in.

  4. You might want to stop background scrolling when the compare view comes up.

  5. Mary Lou… I don’t know who you are. I don’t know what you want… But I will look for you, I will find you, and I will marry you. <3

  6. I don’t know a lot of HTML/CSS/JAVASCRIPT so, how I can put more than three items for the product comparison?

    • * In main.js, replace
      this.itemsAllowed = 3;
      with
      this.itemsAllowed = 4;

      * In component.css, replace
      .compare-basket-active .btn-compare:nth-child(3),
      .compare-basket-active .btn-compare:nth-child(4) {
      background-color: #494BC7;
      color: #fff;
      pointer-events: auto;
      cursor: pointer;
      }
      with
      .compare-basket-active .btn-compare:nth-child(3),
      .compare-basket-active .btn-compare:nth-child(4),
      .compare-basket-active .btn-compare:nth-child(5) {
      background-color: red;
      color: #fff;
      pointer-events: auto;
      cursor: pointer;
      }

  7. So amazingly awesome, great UI. Love the content too, as a former Sommelier it’s quite funny. Got a couple 2000 Branaire-Ducru in the cellar right now 🙂

  8. good Morning! Anyone know how to do to compare two specific items without having to add 1-1?

    Example: they have become the first click of the comparison to inves I go and pick 1-1

  9. How can I make the comparison bar inactive when clicking somewhere on the page?
    Currently it blocks the menu when 1 product is added to the comparison.

  10. Great Job. Keep up the good work. Thanks rascojet for your suggestion on adding more items in the comparison grid.

  11. The plus and check buttons are not showing up! Neither is the remove item form compare basket! I downloaded the theme a few months ago and everything was right then! What went wrong?

    • Hi Shreyas, thanks a lot for the heads-up! We’ve recently switched to HTTPS and some resources still needed their link fixed (Font Awesome). It should all work now, please clean your cache and reload; let us know if it works for you now. Thanks, cheers, ML

  12. I’m having trouble adding multiple product comparisons on the same page. The first works, but all others do nothing.