CodyHouse » UX Patterns https://codyhouse.co A free library of HTML/CSS/Javascript resources to boost your web projects and learn new tricks. Tue, 04 Apr 2017 16:36:48 +0000 en-US hourly 1 http://wordpress.org/?v=4.2.12 Product Builder https://codyhouse.co/gem/product-builder/ https://codyhouse.co/gem/product-builder/#comments Wed, 31 Aug 2016 12:31:00 +0000 https://codyhouse.co/?post_type=gem&p=16220 Product Builder A customizable and responsive product builder for your online store. Product builders are useful shopping tools, that allow potential customers to “build” an ideal version of a product by combining different options. At the end of this process the user is generally given an action to perform: save the build, share it or buy the product directly. Creating such a web component from scratch is no easy work. Therefore we decided to create an easy-to-customize template that can help you realize your own product builder! Photo credits for the demo: BMW.

Creating the structure

The HTML structure is composed of three main elements: a header.main-header for the builder top navigation, a div.cd-builder-steps for the builder steps and a footer.cd-builder-footer for the builder bottom navigation and the overview of the selected product.
<div class="cd-product-builder">
	<header class="main-header">
		<h1>Product Builder</h1>
		
		<nav class="cd-builder-main-nav disabled">
			<ul>
				<li class="active"><a href="#models">Models</a></li>
				<li><a href="#colors">Colors</a></li>
				<li><a href="#accessories">Accessories</a></li>
				<li><a href="#summary">Summary</a></li>
			</ul>
		</nav>
	</header>

	<div class="cd-builder-steps">
		<ul>
			<li data-selection="models" class="active builder-step">
				<section class="cd-step-content">
					<header>
						<h1>Select Model</h1>
						<span class="steps-indicator">Step <b>1</b> of 4</span>
					</header>

					<a href="#0" class="cd-nugget-info">Article &amp; Downaload</a>

					<ul class="models-list options-list cd-col-2">
						<!-- models here -->
					</ul>
				</section>
			</li>
			<!-- additional content will be inserted using ajax -->
		</ul>
	</div>

	<footer class="cd-builder-footer disabled step-1">
		<div class="selected-product">
			<img src="img/product01_col01.jpg" alt="Product preview">

			<div class="tot-price">
				<span>Total</span>
				<span class="total">$<b>0</b></span>
			</div>
		</div>
		
		<nav class="cd-builder-secondary-nav">
			<ul>
				<li class="next nav-item">
					<ul>
						<li class="visible"><a href="#0">Colors</a></li>
						<li><a href="#0">Accessories</a></li>
						<li><a href="#0">Summary</a></li>
						<li class="buy"><a href="#0">Buy Now</a></li>
					</ul>
				</li>
				<li class="prev nav-item">
					<ul>
						<li class="visible"><a href="#0">Models</a></li>
						<li><a href="#0">Models</a></li>
						<li><a href="#0">Colors</a></li>
						<li><a href="#0">Accessories</a></li>
					</ul>
				</li>
			</ul>
		</nav>

		<span class="alert">Please, select a model first!</span>
	</footer>
</div>
As for the builder steps, only the first one can be found in the index.html file. The other steps depend on the model the user selects and are loaded using Ajax (more in the Events handling section).

Adding style

The CSS used for this resource is quite simple, you can download the demo file to take a look at the entire code. Just one note about the style of the main elements of the builder: the div.cd-builder-steps is used to wrap the different builder steps; its list items are in absolute position, have a height and width of 100%, are one on top of the others and are hidden by default; the class .active is then used to reveal the active step.
.cd-builder-steps > ul {
  height: 100%;
  overflow: hidden;
}
.cd-builder-steps .builder-step {
  position: absolute;
  z-index: 1;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: auto;
  visibility: hidden;
  transition: visibility .5s;
}
.cd-builder-steps .builder-step.active {
  position: relative;
  z-index: 2;
  visibility: visible;
}

Events handling

To implement the product builder, we created a ProductBuilder object and used the bindEvents function to attach event handlers to the proper elements.
function ProductBuilder( element ) {
	this.element = element;
	this.stepsWrapper = this.element.children('.cd-builder-steps');
	//...
	this.bindEvents();
}


if( $('.cd-product-builder').length > 0 ) {
	$('.cd-product-builder').each(function(){
		//create a productBuilder object for each .cd-product-builder
		new ProductBuilder($(this));
	});
}
When the user selects one of the models, an Ajax call takes care of loading the new content for the model selected; a data-model attribute is added to each list item inside the ul.models-list and is equal to the name of the HTML file the new content has to be retrieved from (modelType in the code below).
$.ajax({
    type       : "GET",
    dataType   : "html",
    url        : modelType+".html",
    beforeSend : function(){
    	self.loaded = false;
    },
    success    : function(data){
    	self.models.after(data);
    	self.loaded = true;
    	//...
    },
    error     : function(jqXHR, textStatus, errorThrown) {
       //...
    }
});
Each list item has also a data-price, which is the price of the model selected. Data-price attributes are also added to the other options inside the builder (colors, accessories) and are used to update the final price of the product. As for the possible product customizations, we used the .options-list class to target lists of options among which the user can choose (in our example models and accessories). When the user selects an option, the updateListOptions() function takes care of updating the product price (and builder content, if a model has been selected).
//detect click on one element in an options list (e.g, models, accessories)
this.optionsLists.on('click', '.js-option', function(event){
	self.updateListOptions($(this));
});
The class .js-option is added to each list item inside the ul.options-list and is used to detect the click event. If the options are exclusive (like with models since you can select just one), a second class (.js-radio) is added. The .cd-product-customizer class is instead used for customizations like Colors, where the user can select an option and see a change in the product. When one of these options is selected, the customizeModel() function takes care of updating the product preview and the product price (if necessary).
//detect clicks on customizer controls (e.g., colors ...)
this.stepsWrapper.on('click', '.cd-product-customizer a', function(event){
	event.preventDefault();
	self.customizeModel($(this));
})
]]>
https://codyhouse.co/gem/product-builder/feed/ 27
Add to Cart Interaction https://codyhouse.co/gem/add-to-cart-interaction/ https://codyhouse.co/gem/add-to-cart-interaction/#comments Tue, 28 Jun 2016 10:26:34 +0000 https://codyhouse.co/?post_type=gem&p=14417 Add to cart interaction A floating cart that slides in when the user decides to buy an item. We’re used to different patterns when it comes to the “add to cart” process. The basic idea behind this pattern is to notify the user that an item has been added to the cart, and provide her/him with a link to proceed to the checkout. We’ve been experimenting with the idea of hiding the cart by default, and showing it when the user clicks the “add to cart” button. This way the user can either check the cart and proceed to checkout, or continue shopping. The cart will stick to the bottom of the page, accessible at any time. Icons: Nucleoapp.com

Creating the structure

The cart HTML structure is composed of two main elements: a .cd-cart-trigger for the cart trigger and the cart total, and a .cd-cart for the cart content.
<div class="cd-cart-container empty">
	<a href="#0" class="cd-cart-trigger">
		Cart
		<ul class="count"> <!-- cart items count -->
			<li>0</li>
			<li>0</li>
		</ul> <!-- .count -->
	</a>

	<div class="cd-cart">
		<div class="wrapper">
			<header>
				<h2>Cart</h2>
				<span class="undo">Item removed. <a href="#0">Undo</a></span>
			</header>
			
			<div class="body">
				<ul>
					<!-- products added to the cart will be inserted here using JavaScript -->
				</ul>
			</div>

			<footer>
				<a href="#0" class="checkout btn"><em>Checkout - $<span>0</span></em></a>
			</footer>
		</div>
	</div> <!-- .cd-cart -->
</div> <!-- cd-cart-container -->
The unordered list inside the div.body element is empty by default (empty cart); when a product is added to the cart, a list item element is inserted using JavaScript.
<div class="body">
	<ul>
		<li class="product">
			<div class="product-image">
				<a href="#0"><img src="img/thumb.jpg" alt="placeholder"></a>
			</div>

			<div class="product-details">
				<h3><a href="#0">Product Name</a></h3>

				<span class="price">$25.99</span>

				<div class="actions">
					<a href="#0" class="delete-item">Delete</a>

					<div class="quantity">
						<label for="cd-product-'+ productId +'">Qty</label>
						<span class="select">
							<select id="cd-product-'+ productId +'" name="quantity">
								<option value="1">1</option>
								<option value="2">2</option>
								<!-- ... -->
							</select>
						</span>
					</div>
				</div>
			</div>
		</li>

		<!-- other products added to the cart -->
	</ul>
</div>

Adding style

The .cd-cart and .cd-cart-trigger elements are both in position fixed and moved outside the viewport (using a translateY). When an item is added to the cart, the .empty class is removed from the .cd-cart-container and the cart is shown.
.cd-cart-trigger,
.cd-cart {
  position: fixed;
  bottom: 20px;
  right: 5%;
  transition: transform .2s;
}
.empty .cd-cart-trigger, 
.empty .cd-cart {
  /* hide cart */
  transform: translateY(150px);
}
As for the cart animation: we assign a fixed height and width to the div.wrapper element (the same of the a.cd-cart-trigger); when the cart is open, we use the .cart-open class to animate its height and width while revealing the cart content.
.cd-cart .wrapper {
  position: absolute;
  bottom: 0;
  right: 0;
  z-index: 2;
  overflow: hidden;
  height: 72px;
  width: 72px;
  border-radius: 6px;
  transition: height .4s .1s, width  .4s .1s, box-shadow .3s;
  transition-timing-function: cubic-bezier(0.67, 0.17, 0.32, 0.95);
  background: #ffffff;
  box-shadow: 0 4px 30px rgba(0, 0, 0, 0.17);
}

.cart-open .cd-cart .wrapper {
  height: 100%;
  width: 100%;
  transition-delay: 0s;
}
The .deleted class is used to remove an item from the cart: the deleted element has an absolute position, and the cd-item-slide-out animation is used to create the slide-out effect.
.cd-cart .body li.deleted {
  /* this class is added to an item when it is removed form the cart */
  position: absolute;
  left: 1.4em;
  width: calc(100% - 2.8em);
  opacity: 0;
  animation: cd-item-slide-out .3s forwards;
}

@keyframes cd-item-slide-out {
  0% {
    transform: translateX(0);
    opacity: 1;
  }
  100% {
    transform: translateX(80px);
    opacity: 0;
  }
}
If the user clicks on 'Undo', the .deleted class is removed and the element is reinserted in the list.

Events handling

When the user clicks on the .cd-add-to-cart button, the addProduct() function is used to insert a new list item inside the .body > ul element. The product details used are placeholders, which should be replaced by the real product info:
function addProduct() {
  //this is just a product placeholder
  var productAdded = $('<li class="product"><div class="product-image"><a href="#0"><img src="img/product-preview.png" alt="placeholder"></a></div><div class="product-details"><h3><a href="#0">Product Name</a></h3><span class="price">$25.99</span><div class="actions"><a href="#0" class="delete-item">Delete</a><div class="quantity"><label for="cd-product-'+ productId +'">Qty</label><span class="select"><select id="cd-product-'+ productId +'" name="quantity"><option value="1">1</option><option value="2">2</option><option value="3">3</option><option value="4">4</option><option value="5">5</option><option value="6">6</option><option value="7">7</option><option value="8">8</option><option value="9">9</option></select></span></div></div></div></li>');
  cartList.prepend(productAdded);
}
Additional functions, like the updateCartCount() or updateCartTotal(), have been defined to update the cart count and total when new products are added/deleted or when the quantity of a product added to the cart is changed.]]>
https://codyhouse.co/gem/add-to-cart-interaction/feed/ 36
Quick Add To Cart https://codyhouse.co/gem/quick-add-to-cart/ https://codyhouse.co/gem/quick-add-to-cart/#comments Thu, 06 Aug 2015 12:16:46 +0000 http://codyhouse.co/?post_type=gem&p=837 quick-add-to-cart A handy snippet to let users customize a product directly from the products gallery, and add it to the cart. Taking advantage of bigger screens shouldn’t be limited to the use of bigger images/elements. We can put those extra pixels to work by enhancing the user experience with additional features. The standard UX for a product gallery is: user clicks on a product he likes, then navigates to the single product page. From here he’s supposed to select some options, and finally add the product to the cart. Nothing is broken in this process. However, a returning customer, who’s familiar with the products line, may find it handy to select and buy a product directly from the gallery! By showing customers a “quick add to cart” panel while they interact with a product preview, we can reduce the number of steps that separate a user from purchasing an item, and, potentially, increase our conversion rate. Icons from our Nucleo library.

Creating the structure

The main HTML structure is an unordered list. Each list item contains an unordered list (product image slider), a div.cd-customization (with the Add To Cart button and the product customization options) and a div.cd-item-info (with product title and price).
<ul class="cd-gallery">
	<li>
		<div class="cd-single-item">
			<a href="#0">
				<ul class="cd-slider-wrapper">
					<li class="selected"><img src="img/thumb-1.jpg" alt="Preview image"></li>
					<li><img src="img/thumb-2.jpg" alt="Preview image"></li>
					<!-- other product images here -->
				</ul>
			</a>

			<div class="cd-customization">
				<div class="color" data-type="select">
					<ul>
						<li class="color-1 active">color-1</li>
						<li class="color-2">color-2</li>
						<!-- other product colors here -->
					</ul>
				</div>
				
				<div class="size" data-type="select">
					<ul>
						<li class="small active">Small</li>
						<li class="medium">Medium</li>
						<!-- other product sizes here -->
					</ul>
				</div>

				<button class="add-to-cart">
					<em>Add to Cart</em>
					<svg x="0px" y="0px" width="32px" height="32px" viewBox="0 0 32 32">
						<path stroke-dasharray="19.79 19.79" stroke-dashoffset="19.79" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="square" stroke-miterlimit="10" d="M9,17l3.9,3.9c0.1,0.1,0.2,0.1,0.3,0L23,11"/>
					</svg>
				</button>
			</div> <!-- .cd-customization -->

			<button class="cd-customization-trigger">Customize</button>
		</div> <!-- .cd-single-item -->

		<div class="cd-item-info">
			<b><a href="#0">Product Name</a></b>
			<em>$9.99</em>
		</div> <!-- cd-item-info -->
	</li>

	<!-- other list items here -->
</ul> <!-- cd-gallery -->

Adding style

Let's start from the product slider. By default, the list items are in absolute position and translated to the right (outside their .cd-gallery parent) so they are not visible. We then used 2 classes to properly style them: .selected (added to the first list item – visible product image) and .move-left ( product image on the left - not visible).
.cd-slider-wrapper {
  position: relative;
  overflow: hidden;
}
.cd-slider-wrapper li {
  position: absolute;
  top: 0;
  left: 0;
  visibility: hidden;
  /* by default, move the product image to the right*/
  transform: translateX(100%);
  transition: transform 0.3s 0s, visibility 0s 0.3s;
}
.cd-slider-wrapper li.selected {
  /* this is the visible product image */
  position: relative;
  visibility: visible;
  z-index: 1;
  transform: translateX(0);
  transition: transform 0.3s 0s, visibility 0s 0s;
}
.cd-slider-wrapper li.move-left {
  /* move the product image to the left */
  transform: translateX(-100%);
}
About the product customization options: the .cd-customization element is shown when a user hovers over the product; it's in absolute position and placed at the bottom of its .cd-single-item parent element. To create the customization options (color and size), we used two different <ul> elements, both wrapped in a div[data-type="select"] (div.size and div.color). The <ul> element is in absolute position and centered relative to its parent while the div[data-type="select"] has a fixed height (34px) and an overflow: hidden. Each list item inside the unordered list has a height equal to the div[data-type="select"] so that, by default, only the selected option is visible. When a user clicks one of the two customization options, the overflow property of the div[data-type="select"] is set to visible so that the entire <ul> element is shown.
.cd-customization {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  visibility: hidden;
  opacity: 0;
}

.no-touch .cd-single-item:hover .cd-customization {
  /* product customization visible */
  pointer-events: auto;
  visibility: visible;
  opacity: 1;
}

.cd-customization .color, .cd-customization .size {
  height: 34px;
  position: relative;
  overflow: hidden;
}
.cd-customization .color ul, .cd-customization .size ul {
  display: inline-block;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translateX(-50%) translateY(-50%);
  width: 100%;
}
.cd-customization .color.is-open, .cd-customization .size.is-open {
  /* color/size list open - make ul element visible */
  overflow: visible;
}
To make sure that the selected <li> item is always visible, we had to rearrange the list items inside their <ul> parent according to the selected option. To do that, we created the .selected-n class (where n is the item selected). For example, the .selected-3 class is added to the div[data-type="select"] when the third list item is selected:
.cd-customization .color.selected-3 ul li:first-of-type, 
.cd-customization .size.selected-3 ul li:first-of-type {
  /* third option selected in the ul.color/ul.size list */
  transform: translateY(0);
}
.cd-customization .color.selected-3 ul li:nth-of-type(2), 
.cd-customization .size.selected-3 ul li:nth-of-type(2) {
  transform: translateY(100%);
}
.cd-customization .color.selected-3 ul li:nth-of-type(3), 
.cd-customization .size.selected-3 ul li:nth-of-type(3) {
  transform: translateY(-100%);
}
About the Add To Cart animation: the .add-to-cart button is composed by an <em> (button text message) and a svg (check icon). By default, the svg is not visible (it's moved to the right, outside the button). When the product is added to the cart, the .is-added class is added to the .add-to-cart button: the <em> (text) is hidden (moved to the left), while the svg is moved  back inside the button, and the drawing animation starts:
.cd-customization .add-to-cart em {
  /* this is the button text message */
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
.cd-customization .add-to-cart svg {
  /* this is the check icon */
  position: absolute;
  left: 50%;
  top: 50%;
  width: 100%;
  /* move the icon on the right - outside the button */
  transform: translateX(50%) translateY(-50%);
}
.cd-customization .add-to-cart.is-added em {
  /* product added to the cart - hide text message on the left with no transition*/
  color: transparent;
  transform: translateX(-100%);
}
.cd-customization .add-to-cart.is-added svg {
  /* product added to the cart - move the svg back inside the button */
  transform: translateX(-50%) translateY(-50%);
}
About the svg drawing animation, we used the two svg attributes stroke-dasharray and stroke-dashoffset. The first one lets you specify dash length and gaps, while the second one lets you change where the dasharray starts. In our case, we set stroke-dasharray="19.79 19.79" and stroke-dashoffset="19.79" (where 19.76 is the path length):
<svg x="0px" y="0px" width="32px" height="32px" viewBox="0 0 32 32">
	<path stroke-dasharray="19.79 19.79" stroke-dashoffset="19.79" fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="square" stroke-miterlimit="10" d="M9,17l3.9,3.9c0.1,0.1,0.2,0.1,0.3,0L23,11"/>
</svg>
and then animate the dashoffset value to 0 to create the drawing animation. If you're interested in more details about animating svg icons, you can give a look at what we did in the article Animated SVG Icon. Note: to animate the dashoffset value, we could have used css transitions (rather then using the animate() jQuery method), but unfortunately IE11 seems to have a bug with transitions on some svg properties. Last note for touch devices: we used Modernizer to target touch devices and add a .cd-customization-trigger (settings icon) to trigger the.cd-customization element visibility.

Events handling

When a user interacts with one of the product customization options (e.g, the colors option), the .hover class is added to the corresponding .cd-single-item element. This class sets the .cd-customization element to visible, so that it doesn't disappear if the cursor leaves the product (we assumed that the user is interested in the product since he has interacted with it, and doesn't want to lose the options if he accidentally leaves the product). For the same reason, the .hover class is removed from all the other products (so that user can focus only on the product he is interacting with). The resetCustomization() function has been defined to do that:
function resetCustomization(selectOptions) {
	//add .hover class to the item user is interacting with
	//close ul.color/ul.size if they were left open and user is not interacting with them anymore
	//remove the .hover class from items if user is interacting with a different one
	selectOptions.siblings('[data-type="select"]').removeClass('is-open').end().parents('.cd-single-item').addClass('hover').parent('li').siblings('li').find('.cd-single-item').removeClass('hover').end().find('[data-type="select"]').removeClass('is-open');
}
Besides, we used jQuery to implement the product slider (different color images) and the updateCart() function (to update the number of products added to the cart).]]>
https://codyhouse.co/gem/quick-add-to-cart/feed/ 14
Animated Sign Up Flow https://codyhouse.co/gem/animated-sign-up-flow/ https://codyhouse.co/gem/animated-sign-up-flow/#comments Wed, 24 Jun 2015 11:46:30 +0000 http://codyhouse.co/?post_type=gem&p=802 animated sign-up flow A pricing table that animates into a sign up form once the user selects a plan. Designing a checkout process is never easy, there's no universal recipe. If you're offering different product plans, your users will have to navigate through a pricing table first. We created a snippet for responsive pricing tables already. This time we tried to focus on what's next: what happens when the user selects a plan. In most cases, you'll redirect your users to a sign up page. However, if your checkout process is quite simple, an alternative approach would be to animate the pricing table, and show the checkout form right away, in the same page. This is what we tried to achieve with today's snippet. Enough talking, let's dive into the code ;) Resources: Velocity.js by Julian Shapiro. Icons from Nucleo. Creating the structure The html structure is composed by 2 main elements: the ul.cd-pricing, which is the pricing table, and the div.cd-form, which is the form modal window. Each plan includes a header with the plan title and price, a div.cd-pricing-features with a list of features and a footer with a call-to-action button. The form structure is quite straightforward. Just one note: there's an empty div.cd-plan-info element, that will be filled using jQuery with the plan info selected by the user (basically with a clone of div.cd-pricing-header and .cd-pricing-features).
  • Basic

    $9.99 month
    • Feature 1
    • Feature 2
    • Feature 3
    • Feature 4

Need help?

Lorem ipsum dolor sit amet, consectetur adipisicing elit.

Account Info
Adding style CSS for this resource is quite simple. Just some hints: the class .empty-box is added to the .cd-pricing > li item when the form becomes visible. Since we clone the entire list item (and place it inside the form), the original one is still there. In this case, we used the .empty-box class to hide the original list item. .cd-pricing > li { position: relative; margin: 0 auto 2.5em; background-color: #ffffff; border-radius: .3em .3em .25em .25em; box-shadow: 0 2px 8px rgba(2, 4, 5, 0.5); } .cd-pricing > li.empty-box { box-shadow: none; } .cd-pricing > li.empty-box::after { /* placeholder visible when .cd-form is open - in this case same color of the background */ content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: #0f222b; } We use the .empty-box class also to target the action button in the footer, so that it animates back when the user closes the form (from scale(0) to scale(1)). .cd-pricing-footer a { transition: transform 0.3s; } .empty-box .cd-pricing-footer a { /* scale down to 0 the action button when sign up form is visible */ transform: scale(0); } The div.cd-form is in position: fixed; and it doesn't have a size by default. When the user taps/clicks on the select button, we make the form visible (from visibility: hidden; to visibility: visible;), assign it the same size of the pricing list item, and animate it by making it bigger. This creates the illusion that it's the pricing item itself that enlarges, while it's the div.cd-form, that has the same size and contains the same elements (clones - that then move to make space for the sign up form). /* -------------------------------- Form -------------------------------- */ .cd-form { position: fixed; z-index: 2; background-color: #ffffff; border-radius: .25em; visibility: hidden; transition: visibility 0s 0.8s; /* Force Hardware Acceleration in WebKit */ transform: translateZ(0); backface-visibility: hidden; } .cd-form.is-visible { /* form is visible */ visibility: visible; transition: visibility 0s 0s; } One last detail worth mentioning: when the form becomes visible, the green background of the plan title animates to become the background of the features list as well (desktop only). That is actually a different element (.cd-form .cd-pricing-features::before) whose height is animated (using the scale transformation). .cd-form .cd-pricing-features::before { /* this is the layer which covers the .cd-pricing-features when the form is open - visible only on desktop */ content: ''; position: absolute; /* fix a bug while animating - 1px white space visible */ top: -5px; left: 0; height: calc(100% + 5px); width: 100%; background-color: #95ac5f; will-change: transform; transform: scaleY(0); transform-origin: center top; transition: transform 0.6s 0.2s; } Events handling The animateForm() function has been defined to animate the modal form: when a user selects a plan, the function evaluates the position and dimension of the selected pricing table item and assign them to the .cd-form so that it completely covers the pricing item (which is hidden using the .empty-box class). Then the animation starts: we animate the .cd-form width and height to its final values and translate it so that it’s centered in the viewport. //form is the .cd-form element form.velocity( { 'width': tableWidth+'px', //pricing table item width 'height': tableHeight+'px', //pricing table item height 'top': formTopValue, //final top value of the form 'left': formLeftValue, //final top value of the form 'translateX': formTranslateX+'px', //difference between formLeftValue and pricing table item left value 'translateY': formTranslateY+'px', //difference between formTopValue and pricing table item top value 'opacity': 1, }, 0, function(){ //table is the pricing table item table.addClass('empty-box'); form.velocity( { 'width': formFinalWidth+'px', //form final width 'height': formFinalHeight+'px', //form final height 'translateX': 0, 'translateY': 0, }, //animation duration animationDuration, //spring easing [ 220, 20 ]).addClass('is-visible'); }); When the user closes the modal, the form fieldsets are hidden (changing their opacity to 0) and then the reverse animation is performed (the delay for the animation is defined using the delay variable).]]>
https://codyhouse.co/gem/animated-sign-up-flow/feed/ 17
Product Preview Slider https://codyhouse.co/gem/product-preview-slider/ https://codyhouse.co/gem/product-preview-slider/#comments Fri, 20 Mar 2015 11:43:20 +0000 http://codyhouse.co/?post_type=gem&p=626 product-slider An easy way to show more product images and variations right in the product gallery. While you scroll through pages of products, you often base your decision to “know more” about a product on pictures only. Visual impact, in particular on mobile devices, is essential to everyone’s buying process. What if a product is available in different colors? This is a piece of information generally not available to the user until he/she is willing to go to the product page. A different UX approach, the one of today’s resource, is to let the user interact with the product preview right in the main gallery page. A simple slider, to check product variations before jumping into the product page, could be a way to increase conversion rates. Beautiful product pictures from Ugmonk.

Creating the structure

The HTML structure is an unordered list. Each list item contains a nested unordered list (with product images) and the product info (title and price).
<ul class="cd-gallery">
	<li>
		<a href="http://codyhouse.co/">
			<ul class="cd-item-wrapper">
				<li class="selected">
					<img src="img/ugmonk-tshirt-1.jpg" alt="Preview image">
				</li>

				<li class="move-right" data-sale="true" data-price="$22">
					<img src="img/ugmonk-tshirt-2.jpg" alt="Preview image">
				</li>

				<li>
					<img src="img/ugmonk-tshirt-3.jpg" alt="Preview image">
				</li>
			</ul> <!-- cd-item-wrapper -->
		</a>

		<div class="cd-item-info">
			<b><a href="#0">Mountains</a></b>

			<em class="cd-price">$26</em>
		</div> <!-- cd-item-info -->
	</li>

	<!-- other list items here -->
</ul> <!-- cd-gallery -->
Note that the .cd-dots elements (dots navigations for each product slider) and the .cd-new-price (new price if product is on sale) are not directly inserted in the html but created using jQuery.

Adding style

On small screens, the product preview images are visible by default: we assume the user will see one product at a time so he/she won't get distracted. User can then browse through them both by swiping or clicking on the preview images. By default, the list items are in absolute position and translated on the right (outside their .cd-gallery parent) so they are not visible. We then declared 4 classes to properly style them: .selected (added to the first list item - main visible image), .move-right (added to the second list item - preview image on the right), .move-left (preview image on the left) and .hide-left (hidden items on the left).
.cd-item-wrapper li {
  position: absolute;
  top: 0;
  left: 25%;
  width: 50%;
  opacity: 0;
  transform: translateX(200%) scale(0.7);
}
.cd-item-wrapper li.selected {
  /* selected item */
  position: relative;
  opacity: 1;
  transform: translateX(0) scale(1.3);
}
.cd-item-wrapper li.move-right {
  /* item on right - preview visible */
  transform: translateX(100%) scale(0.7);
  opacity: 0.3;
}
.cd-item-wrapper li.move-left {
  /* item on left - preview visible */
  transform: translateX(-100%) scale(0.7);
  opacity: 0.3;
}
.cd-item-wrapper li.hide-left {
  /* items hidden on the left */
  transform: translateX(-200%) scale(0.7);
}
On bigger screens, user sees more products at a time so we decided to hide the preview images to assure a cleaner experience. When user hovers over one of the product, we assume his focus is on that particular product so the preview images are shown. 3 additional classes have been declared: .hover (assigned to the preview item when user hovers over it), .focus-on-right, (assigned to the .selected and .move-left items when user hovers over the .move-right element) and .focus-on-left (assigned to the .selected and .move-right items when user hovers over the .move-left element).
@media only screen and (min-width: 1048px) {
  .cd-item-wrapper li.move-left,
  .cd-item-wrapper li.move-right {
    /* hide preview items */
    opacity: 0;
  }
  .cd-item-wrapper li.focus-on-left {
    /* class added to the .selected and .move-right items when user hovers over the .move-left item (item preview on the left) */
    transform: translateX(3%) scale(1.25);
  }
  .cd-item-wrapper li.focus-on-left.move-right {
    transform: translateX(103%) scale(0.7);
  }
  .cd-item-wrapper li.focus-on-right {
    /* class added to the .selected and .move-left items when user hovers over the .move-right item (item preview on the right) */
    transform: translateX(-3%) scale(1.25);
  }
  .cd-item-wrapper li.focus-on-right.move-left {
    transform: translateX(-103%) scale(0.7);
  }
  .cd-item-wrapper li.hover {
    /* class added to the preview items (.move-left or .move-right) when user hovers over them */
    opacity: 1;
  }
  .cd-item-wrapper li.hover.move-left {
    transform: translateX(-97%) scale(0.75);
  }
  .cd-item-wrapper li.hover.move-right {
    transform: translateX(97%) scale(0.75);
  }
}

Events handling

We used jQuery to implement the product image slider (with touch swipe navigation, previous/next and dots navigation). Besides, we defined the updatePrice() function to update the product price (if on sale). This function checks if the selected item is on sale (data-sale="true") and, if that's the case, adds the on-sale class to the .cd-price element (crossing line visible on the old price) and inserts a new .cd-new-price element (its text equals to the selected item data-price). You can see the live effect in our demo on the t-shirt product.
function updatePrice(container, n) {
	//container -> each one of the $('.cd-gallery').children('li')
	//n -> index of the selected item in the .cd-item-wrapper
	var priceTag = container.find('.cd-price'),
		selectedItem = container.find('.cd-item-wrapper li').eq(n);
	if( selectedItem.data('sale') ) { 
		// if item is on sale - cross old price and add new one
		priceTag.addClass('on-sale');
		var newPriceTag = ( priceTag.next('.cd-new-price').length > 0 ) ? priceTag.next('.cd-new-price') : $('<em class="cd-new-price"></em>').insertAfter(priceTag);
		newPriceTag.text(selectedItem.data('price'));
		setTimeout(function(){ newPriceTag.addClass('is-visible'); }, 100);
	} else {
		// if item is not on sale - remove cross on old price and sale price
		priceTag.removeClass('on-sale').next('.cd-new-price').removeClass('is-visible').on('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function(){
			priceTag.next('.cd-new-price').remove();
		});
	}
}
]]>
https://codyhouse.co/gem/product-preview-slider/feed/ 42
Secondary sliding navigation https://codyhouse.co/gem/secondary-sliding-navigation/ https://codyhouse.co/gem/secondary-sliding-navigation/#comments Thu, 26 Feb 2015 12:20:58 +0000 http://codyhouse.co/?post_type=gem&p=597 secondary-sliding-navigation A bold, secondary menu that slides over the main navigation. Today’s resource is a simple, handy snippet: a secondary navigation that slides down, replacing the main navigation links. This approach can be an alternative to a standard dropdown menu, in particular if you want to emphasise more the sub navigation. Besides you can easily customize the snippet and use the slide in panel for a search box instead, or a login form - just to give you a couple of ideas.

Creating the structure

The HTML is structured in 2 main elements: an <header> wrapping the main navigation, and a <main> containing the page content. The main navigation is composed of 2 nested unordered lists, semantically wrapped in a <nav> element.
<header>
	<div class="cd-logo"><a href="#0"><img src="img/cd-logo.svg" alt="Logo"></a></div>

	<nav class="cd-main-nav-wrapper">
		<ul class="cd-main-nav">
			<li><a href="#0">About</a></li>
			<!-- other list items here -->
			<li>
				<a href="#0" class="cd-subnav-trigger"><span>Categories</span></a>

				<ul>
					<li class="go-back"><a href="#0">Menu</a></li>
					<li><a href="#0">Category 1</a></li>
					<!-- other list items here -->
				</ul>
			</li>
		</ul>
	</nav> 
	
	<a href="#0" class="cd-nav-trigger">Menu<span></span></a>
</header>

<main class="cd-main-content">
	<!-- main content here -->
</main>

Adding style

On small devices, the main navigation is on the right side, hidden by default; when user clicks the menu icon, the <main> and <header> elements translate to the left (nav-is-visible class is applied) to reveal the navigation. When user clicks the .cd-subnav-trigger, the main navigation is pushed to the left and replaced by the secondary navigation.
header.nav-is-visible {
  transform: translateX(-260px);
}
.cd-main-content.nav-is-visible {
  transform: translateX(-260px);
}
.cd-main-nav {
  position: fixed;
  top: 0;
  right: 0;
  width: 260px;
  visibility: hidden;
}
.cd-main-nav.nav-is-visible {
  visibility: visible;
}
.cd-main-nav li ul {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  transform: translateX(260px);
}
.cd-main-nav.moves-out > li > a {
  /* push the navigation items to the left - and lower down opacity - when secondary nav slides in */
  transform: translateX(-100%);
  opacity: 0;
}
.cd-main-nav.moves-out > li > ul {
  /* reveal secondary nav */
  transform: translateX(0);
}
On desktop devices (viewport width more than 1024px), the secondary navigation is placed on top of the header (outside the viewport) and slides in covering the main navigation. We assigned a higher z-index to the .cd-logo and .cd-subnav-trigger so that they remain visible after the sub navigation has slided in. Besides, we assigned the sub navigation the same padding as the .cd-main-nav  and inserted a .placeholder element as last list item occupying the same space of the .cd-subnav-trigger: this way we make sure the sub navigation list items don't cover both the logo and the .cd-subnav-trigger.
@media only screen and (min-width: 1024px) {
  .cd-main-nav {
    height: 80px;
    /* padding left = logo size + logo left position*/
    padding: 0 5% 0 calc(5% + 124px);
    text-align: right;
  }
  .cd-main-nav li ul {
    height: 80px;
    background-color: #7e4d7e;
    /* padding left = logo size + logo left position*/
    padding: 0 5% 0 calc(5% + 124px);
    transform: translateY(-80px);
    transition: transform 0.3s 0.2s;
  }
  .cd-main-nav li ul li {
    opacity: 0;
    transform: translateY(-20px);
    transition: transform 0.3s 0s, opacity 0.3s 0s;
  }
  .cd-main-nav .placeholder {
    /* never visible or clickable- it is used to take up the same space as the .cd-subnav-trigger */
    display: block;
    visibility: hidden;
    opacity: 0;
    pointer-event: none;
  }
  .cd-main-nav.moves-out > li > ul {
    transition: transform 0.3s;
    transform: translateY(0);
  }
  .cd-main-nav.moves-out > li ul li {
    opacity: 1;
    transform: translateY(0);
    transition: transform 0.3s 0.2s, opacity 0.3s 0.2s;
  }
}

Events handling

We used jQuery to add/remove classes according to specific events. The only important thing to note is that in the starting HTML structure the navigation is inside the <header>. On mobile, we wanted the navigation to be on the side, hidden by default, and it was easier for us to have it outside the header. So we use jQuery to move the navigation outside the header on small devices.]]>
https://codyhouse.co/gem/secondary-sliding-navigation/feed/ 63
Expandable Image Gallery https://codyhouse.co/gem/expandable-image-gallery/ https://codyhouse.co/gem/expandable-image-gallery/#comments Wed, 10 Dec 2014 12:17:30 +0000 http://codyhouse.co/?post_type=gem&p=476 expandable-image-gallery An image that expands on click, going full-width and turning into a gallery. We often see the "2 blocks modules" design approach: 50% width image on one side, and text on the other. These modules can be used for an About us section, to explain product features, or, like in our example, for the product preview image and information sections. Generally the user can't interact with the image. But what if you want to show more than one picture? An option could be to fire a modal slideshow on click. CSS Transitions allow new UX solutions though. A new approach could be to expand the image, make it full-width by covering adjacent content and pushing down lower positioned content, and turning it into a full-width slideshow! Inspiration came from this Dribbble shot by Jonathan Howell.

Creating the structure

The HTML is structured in 2 main <div> elements (.cd-slider-wrapper and .cd-item-info) – the first containing the image gallery and the second the product info (title, action button..) – wrapped inside a section.cd-single-item. The remaining content is inserted in a separate .cd-content section.
<section class="cd-single-item">
	<div class="cd-slider-wrapper">
		<ul class="cd-slider">
			<li class="selected"><img src="img/img-1.jpg" alt="Product Image 1"></li>
			<li><img src="img/img-2.jpg" alt="Product Image 1"></li>
			<li><img src="img/img-3.jpg" alt="Product Image 2"></li>
		</ul> <!-- cd-slider -->

		<ul class="cd-slider-navigation">
			<li><a href="#0" class="cd-prev inactive">Next</a></li>
			<li><a href="#0" class="cd-next">Prev</a></li>
		</ul> <!-- cd-slider-navigation -->

		<a href="#0" class="cd-close">Close</a>
	</div> <!-- cd-slider-wrapper -->

	<div class="cd-item-info">
		<h2>Produt Title</h2>
		<p>Lorem ipsum dolor sit amet...</p>
		<button class="add-to-cart">Add to cart</button>						
	</div> <!-- cd-item-info -->
</section> <!-- cd-single-item -->

<section class="cd-content">
	<!--  other content here -->
</section>
Note that the .cd-slider-pagination element (navigation for paging control of each slider) is not directly inserted in the html but created using jQuery.

Adding Style

On small devices the CSS is pretty straightforward: both .cd-slider-wrapper and .cd-item-info are in full width and follow the standard flow of the page. On desktop devices (viewport width more than 1024px) we assigned a position: absolute and width: 50% to the .cd-item-info and placed it on the right side of the screen. For the .cd-slider-wrapper element, we set width: 50%. When user clicks on the image gallery, we add the .cd-slider-active class to the .cd-single-item section: the .cd-slider-wrapper width is set to 100%; this way the .cd-slider-wrapper enlarges, covering the .cd-item-info (which is in position: absolute) and pushing down the remaining content. CSS3 Transition to the width value has been added in order to achieve the smooth animation.
@media only screen and (min-width: 1024px) {
  .cd-slider-wrapper {
    transition: width 0.4s;
    width: 50%; 
  }
  .cd-slider-active .cd-slider-wrapper {
    width: 100%; 
  } 
}
@media only screen and (min-width: 1024px) {
  .cd-item-info {
    position: absolute;
    width: 50%;
    top: 0;
    right: 0;
    padding: 60px 60px 0;
  } 
}
For this technique to work properly, the gallery images should have an aspect ratio higher than 1 (width higher than height); when the image is in slideshow mode, its height is increased proportionally to its width. Therefore an image with aspect ratio minor than 1 would cause more scrolling. Besides, since the .cd-item-info is in position: absolute, the height of the .cd-single-item is set by the gallery image height only; consequently the .cd-item-info height has to be smaller that the gallery image one.

Events Handling

We used jQuery to trigger the slideshow mode when user clicks the preview image. Besides, we implemented a basic slider for the image gallery (with keyboard and touch swipe navigation, previous/next and paging navigation).]]>
https://codyhouse.co/gem/expandable-image-gallery/feed/ 69
Responsive Newsletter Form https://codyhouse.co/gem/responsive-newsletter-form/ https://codyhouse.co/gem/responsive-newsletter-form/#comments Thu, 14 Aug 2014 10:18:41 +0000 http://codyhouse.co/?post_type=gem&p=284 responsive newsletter form A minimal and responsive newsletter form with the addition of some subtle CSS3 animations to enrich the user experience. It's always challenging to push a user to subscribe to your website newsletter. The real key is where you position the call-to-action form IMO. Then there's the UI and UX of the form itself. When the user decides to subscribe, we need to make sure the process is smooth and simple. Our approach to this resource was: how can we create the most minimal form possible? Finally we went with a bare bone starting structure (big email input field and a small title), then we started experimenting with some subtle CSS3 animations to enrich the form while the user is interacting with it. Here is a quick animation we put together to show the whole process: newsletter animation

Creating the structure

The structure is pretty basic: labels and input fields are wrapped inside the <form>, notification messages are 3 separate divs. You may notice we used a <label> element for the title. It's not semantically correct, the <label> should describe what the <input> is about. Since the title is inside the input field tough, by using a <label> element we make sure that if the user taps/clicks on the title, the email input becomes focused too. The empty .cd-loading element is used to create the animated loading bar.
<div class="cd-form-wrapper cd-container">
	<form class="cd-form">
		<label class="cd-label" for="cd-email">Newsletter</label>
		<input type="email" id="cd-email" class="cd-email" name="cd-email" placeholder="Enter your email address">
		<input type="submit" class="cd-submit" value="Submit">
		<div class="cd-loading"></div>
	</form>

	<div data-type="message" class="cd-response cd-response-error">Ops! Error message here</div>
	<div data-type="message" class="cd-response-success"><p>Great! Success message here</p></div>
	<div data-type="message" class="cd-response cd-response-notification">Hey! Notification message here</div>
</div>

Adding style

Below are some highlights about how we created the animations in CSS. It's mostly based on CSS3 Transitions, i.e. the .cd-email element (the email input field) takes the full form size at the beginning. While the user is typing the email address, we use jQuery to add the class .is-active to the form, and we change the bottom value for the .cd-email element from 0 to 50%. The transition applied to the bottom property makes the movement smooth. We used a CSS3 Animation for the .cd-submit element only (submit button), in order to achieve the bounce effect. If that's too much, feel free to use a transition instead.
.cd-form {
  position: relative;
}

.cd-form .cd-loading {
  /* loading bar */
  position: absolute;
  bottom: 0;
  left: 0;
  height: 3%;
  width: 100%;
  transform-origin: 0 50%;
  transform: scaleX(0);
  visibility: hidden;
  transition: transform 3s;
}

.cd-form.is-submitted .cd-loading {
  visibility: visible;
  transform: scaleX(1);
}

.cd-email {
  top: 0;
  left: 0;
  width: 100%;
  bottom: 0;
  transition: bottom 0.3s, background-color 0.3s;
  z-index: 1;
}

.is-active .cd-email {
  bottom: 50%;
}

.cd-submit {
  top: 50%;
  /* hidden by default */
  display: none;
  transition: background-color 0.2s;
  z-index: 2;
}

.is-active .cd-submit {
  display: block;
  animation: cd-bounce-in ease-out 0.4s;
}

@keyframes cd-bounce-in {
  0% {
    top: 100%;
  }

  60% {
    top: 45%;
  }

  100% {
    top: 50%;
  }
}

Events Handling

We use the keyup() event to check what value user is entering in the email <input> field: if he has typed a '@' followed by a dot, we show the submit button giving the .is-active class to the <form> element.
$('.cd-form .cd-email').keyup(function(event){	
	var emailInput = $(this),
		insertedEmail = emailInput.val(),
		atPosition = insertedEmail.indexOf("@");
    	dotPosition = insertedEmail.lastIndexOf(".");
    //check if user has inserted a "@" and a dot
    if (atPosition< 1 || dotPosition<atPosition+2 ) {
    	//if he hasn't..
    	//hide the submit button
    	$('.cd-form').removeClass('is-active').find('.cd-loading').off('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend');
    } else {
    	//if he has..
    	//show the submit button
    	$('.cd-form').addClass('is-active');
    }
});
When user submits the form, we add the .is-submitted class to show the loading bar and wait for the end of the transition to show the success/error message. When integrating your email service provider code, though, you should replace this function with an ajax call, using, for example, the beforeSend function to show the loading bar and the success and error function to show the success/error/notification message.]]>
https://codyhouse.co/gem/responsive-newsletter-form/feed/ 27