Quantcast
Channel: CodyHouse » Web Components
Viewing all 26 articles
Browse latest View live

Radial SVG Slider

$
0
0
Radial SVG Slider A simple, responsive slider, with a radial transition effect powered by SVG clipPath and mask elements. While browsing Dribbble in search of inspiration, we came across this interesting animation created by Tokito. The main idea is to show a little preview of the following slide, then scale it up when the user interacts with it. In order to create this effect, SVG clipping and masking elements sounded like the perfect allies. Let’s break this up in steps: first of all, we needed to show a preview of both previous and next slides. Therefore, for each slide, we needed 2 paths: a circle element to clip the part of the image visible at the beginning (the navigation CTA), and a second circle element that covers the entire slider (obtained by increasing the radius of the first circle element). Here is a preview of the 2 paths created in Illustrator on 2 separate artboards, and then exported as SVG files. You’ll find these vectors inside the source files. clip-path By using the clipPath element, you can define the visible area of an image. Then, obviously, if you animate the clipPath element, you animate the visible area of the image you’re applying the clip to. clip-animation-01 Now the tricky part: by animating the circle element the user interacts with, we cover the entire slider, including the opposite round/navigation item. To fix that, we used the SVG mask element: basically we set an area where the animated circle element won’t be visible, no matter what . This mask is equal to the size of the round/navigation element we don’t want to cover. Masks use transparency. Therefore, using a vector graphic tool, we created a white path that covers the entire SVG viewport, except the area we want to mask out. mask Here is a quick animation we put together to show you the idea behind the clipping + masking. clip-animation-02 Images: Unsplash

Creating the structure

The HTML structure is composed of two unordered lists: a ul.cd-radial-slider for the slides and a ul.cd-radial-slider-navigation for the slider navigation. Each list item inside the ul.cd-radial-slider is composed of two main elements: a .svg-wrapper containing a svg with a  <clipPath> element (used to change the clipping area of the slide image) and an <image> element (whose clip-path url attribute is the <clipPath> id), and a .cd-radial-slider-content for the slide content. An additional .cd-round-mask is used to wrap the two <mask> elements.
<div class="cd-radial-slider-wrapper">
	<ul class="cd-radial-slider" data-radius1="60" data-radius2="1364" data-centerx1="110" data-centerx2="1290">
		<li class="visible">
			<div class="svg-wrapper">
				<svg viewBox="0 0 1400 800">
					<title>Animated SVG</title>
					<defs>
						<clipPath id="cd-image-1">
							<circle id="cd-circle-1" cx="110" cy="400" r="1364"/>
						</clipPath>
					</defs>

					<image height='800px' width="1400px" clip-path="url(#cd-image-1)" xlink:href="img/img-1.jpg"></image>
				</svg>
			</div> <!-- .svg-wrapper -->

			<div class="cd-slider-content">
				<div class="wrapper">
					<div>
						<h2>Slide #1 Title</h2>
						<p>Lorem ipsum dolor sit amet, consectetur.</p>
						<a href="#0" class="cd-btn">Learn More</a>
					</div>
				</div>
			</div> <!-- .cd-slider-content -->
		</li>

		<li class="next-slide">
			<!-- ... -->
		</li>

		<!-- additional slides here -->

	</ul> <!-- .cd-radial-slider -->

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

	<div class="cd-round-mask">
		<svg viewBox="0 0 1400 800">
			<defs>
				<mask id="cd-left-mask" height='800px' width="1400px" x="0" y="0" maskUnits="userSpaceOnUse">
					<path fill="white" d="M0,0v800h1400V0H0z M110,460c-33.137,0-60-26.863-60-60s26.863-60,60-60s60,26.863,60,60S143.137,460,110,460z"/>
			    </mask>

			    <mask id="cd-right-mask" height='800px' width="1400px" x="0" y="0" maskUnits="userSpaceOnUse">
					<path fill="white" d="M0,0v800h1400V0H0z M1290,460c-33.137,0-60-26.863-60-60s26.863-60,60-60s60,26.863,60,60S1323.137,460,1290,460z"/>
			    </mask>
			</defs>
		</svg>
	</div>
</div> <!-- .cd-radial-slider-wrapper -->

Adding style

The slider structure is quite basic: all slides have an opacity: 0, are in absolute position and are placed one on top of the other (top: 0 and left:0). The .visible class is added to the selected slide (at the end of the clipping animation) to change its position from absolute to relative, while the .is-animating class is added to the slide during the clipping animation to change its z-index. The .next-slide and .prev-slide classes are instead used to show a preview of both the previous and next slides. Two additional classes have been used to animate the navigation round elements: a .scale-down class to hide the slide preview when a new slide is selected (scale-down effect) and a .move-up class used to create the click effect when one of the slide previews is clicked on.
.cd-radial-slider > li {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  opacity: 0;
  transition: transform .2s;
}
.cd-radial-slider > li.visible {
  position: relative;
  opacity: 1;
}
.cd-radial-slider > li.is-animating,
.cd-radial-slider > li.prev-slide,
.cd-radial-slider > li.next-slide {
  opacity: 1;
}
.cd-radial-slider > li.is-animating {
  z-index: 2;
}
.cd-radial-slider > li.scale-down,
.cd-radial-slider > li.move-up {
  z-index: 3;
}
.cd-radial-slider > li.move-up {
  /* class added to the navigation round element when clicked - used to create the click effect */
  animation: cd-clicked .2s;
}
.cd-radial-slider > li.scale-down {
  /* class added to the navigation round element to create the scale down effect  */
  transform: scale(0);
}
.cd-radial-slider > li.next-slide {
  /* for the scale-down/click effect - change the transform origin so that it is the center of the navigation round element */
  transform-origin: 92.14% 50%;
}
.cd-radial-slider > li.prev-slide {
  transform-origin: 7.86% 50%;
}

Events handling

To implement the radial SVG slider, we created a radialSlider object and used the bindEvents function to attach event handlers for the click to the slider navigation:
var radialSlider = function(element) {
	this.element = element;
	this.slider = this.element.find('.cd-radial-slider');
	this.slides = this.slider.children('li');
	//...
	this.navigation = this.element.find('.cd-radial-slider-navigation');
	//...
	this.bindEvents();
}

radialSlider.prototype.bindEvents = function() {
	var self = this;

	//update visible slide when clicking the navigation round elements
	this.navigation.on('click', function(event){
		if( !self.animating ) {
			self.animating =  true;
			event.preventDefault();
			var direction = ( $(event.target).hasClass('next') ) ? 'next' : 'prev';
			//update radialSlider index properties
			self.updateIndexes(direction);
			//show new slide
			self.updateSlides(direction);
		}
	});
}
To animate the slide image clipping area, we animated the 'r' attribute of the <circle> element inside the <clipPath>. We added to the .cd-radial-slider element a data-radius1 and a data-radius2 attribute to easily retrieve the initial and final radius values, and a data-centerx1 and data-centerx2 for the <circle> center (two different values for the next and previous slide preview). We then used the animate() method provided by Snap.svg to animate the circle element.
clipPathVisible.animate({'r': radius2}, duration, customMinaAnimation, function(){
	//callback function here
});
The easing function is a custom cubic-bezier function; unfortunately, this is something which is not available by default in Snap.svg, but you can create a custom timing function from your custom cubic-bezier (here’s a StackOverflow post that covers it in details). To apply a mask to the visible slide, instead, we changed the style attribute of the svg <image> element. For example, to mask the slide so that the next slide preview is visible we used:
this.slides.eq(this.visibleIndex).find('image').attr('style', 'mask: url(#'+this.rightMask.attr('id')+')');
where this.slides.eq(this.visibleIndex) is the visible slide and this.rightMask.attr('id') is the id of the <mask> element.

Add to Cart Interaction

$
0
0
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.

Auto-Hiding Navigation

$
0
0
Auto-hiding Navigation A simple navigation that auto-hides when the user scrolls down, and becomes visible when the user scrolls up. Auto-hiding navigations have been around for quite some time now, in particular on mobile devices. The idea behind this UX pattern is simple yet efficient: we want the navigation to be easy to reach all the time, so we stick it on top. However, we auto-hide it when the user scrolls down, to create more room for the content. If the user scrolls up, we interpret his behaviour as will to access the navigation, so we bring it back. Since we’ve been using this approach in several clients’ projects, we thought it would be handy to have a ready-to-use snippet here on CodyHouse. Images: Unsplash

Creating the structure

The HTML structure is composed of a header.cd-auto-hide-header element used to wrap the primary navigation (nav.cd-primary-nav) and a main.cd-main-content for the page main content.
<header class="cd-auto-hide-header">
	<div class="logo"><a href="#0"><img src="img/cd-logo.svg" alt="Logo"></a></div>

	<nav class="cd-primary-nav">
		<a href="#cd-navigation" class="nav-trigger">
			<span>
				<em aria-hidden="true"></em>
				Menu
			</span>
		</a> <!-- .nav-trigger -->

		<ul id="cd-navigation">
			<li><a href="#0">The team</a></li>
			<li><a href="#0">Our Services</a></li>
			<li><a href="#0">Our Projects</a></li>
			<li><a href="#0">Contact Us</a></li>
		</ul>
	</nav> <!-- .cd-primary-nav -->
</header> <!-- .cd-auto-hide-header -->

<main class="cd-main-content">
	<!-- content here -->
</main> <!-- .cd-main-content -->
If the page has a sub-navigation, an additional nav.cd-secondary-nav is inserted inside the header element:
<header class="cd-auto-hide-header">
	<div class="logo"><a href="#0"><img src="img/cd-logo.svg" alt="Logo"></a></div>

	<nav class="cd-primary-nav">
		<a href="#cd-navigation" class="nav-trigger">
			<span>
				<em aria-hidden="true"></em>
				Menu
			</span>
		</a> <!-- .nav-trigger -->

		<ul id="cd-navigation">
			<!-- links here -->
		</ul>
	</nav> <!-- .cd-primary-nav -->

	<nav class="cd-secondary-nav">
		<ul>
			<li><a href="#0">Intro</a></li>
			<!-- additional links here -->
		</ul>
	</nav> <!-- .cd-secondary-nav -->
</header> <!-- .cd-auto-hide-header -->

<main class="cd-main-content sub-nav">
	<!-- content here -->
</main> <!-- .cd-main-content -->
Finally, if the secondary navigation is below a hero block, a .cd-hero element is inserted right below the <header>, followed by the .cd-secondary-nav element:
<header class="cd-auto-hide-header">
	<div class="logo"><a href="#0"><img src="img/cd-logo.svg" alt="Logo"></a></div>

	<nav class="cd-primary-nav">
		<a href="#cd-navigation" class="nav-trigger">
			<span>
				<em aria-hidden="true"></em>
				Menu
			</span>
		</a> <!-- .nav-trigger -->

		<ul id="cd-navigation">
			<!-- links here -->
		</ul>
	</nav> <!-- .cd-primary-nav -->
</header> <!-- .cd-auto-hide-header -->

<section class="cd-hero">
	<!-- content here -->
</section> <!-- .cd-hero -->

<nav class="cd-secondary-nav">
	<ul>
		<!-- links here -->
	</ul>
</nav> <!-- .cd-secondary-nav -->

<main class="cd-main-content sub-nav-hero">
	<!-- content here -->
</main> <!-- .cd-main-content -->

Adding style

We used the .cd-auto-hide-header class to define the main style of the auto-hiding header. By default, the header has a fixed position and a top of zero; when the user starts scrolling down, the .is-hidden class is used to hide the header right above the viewport.
.cd-auto-hide-header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 60px;
  transition: transform .5s;
}
.cd-auto-hide-header.is-hidden {
  transform: translateY(-100%);
}
In the style.css file (or style.scss if you are using Sass) the code you find right below the comment '1. Auto-Hiding Navigation - Simple' is the one you need to include in your project if you are using the 'Simple' auto-hiding navigation (primary navigation only). If your header has a sub-navigation (right below the primary navigation), then you need to include also the style you find under the '2. Auto-Hiding Navigation - with Sub Nav' comment. This second block of code is used to define the main style for the secondary navigation (using the .cd-secondary-nav class). Finally, if your secondary navigation is below a hero section, in addition to the previous two blocks you need to include also the code under the '3. Auto-Hiding Navigation - with Sub Nav + Hero Image' section. This is used to define two classes, .fixed and .slide-up, which are added to the secondary navigation while scrolling (the first one to make it 'sticky' and the second to slide it up when the primary navigation is hidden).
.cd-secondary-nav.fixed {
  position: fixed;
  top: 60px;
}
.cd-secondary-nav.slide-up {
  transform: translateY(-60px);
}
Finally, the 'Main content'  and 'Intro Section' blocks are used to define the basic style for the .cd-main-content and .cd-hero elements (mostly padding/margin to account for the fixed header).

Events handling

We use jQuery to listen for the scroll event on the window object.
var scrolling = false;
$(window).on('scroll', function(){
	if( !scrolling ) {
		scrolling = true;
		(!window.requestAnimationFrame)
			? setTimeout(autoHideHeader, 250)
			: requestAnimationFrame(autoHideHeader);
	}
});
The autoHideHeader() function takes care of hiding/revealing the navigation according to whether the user is scrolling up or down.

Clipped SVG Slider

$
0
0
SVG Clipped Slider A simple slider, with morphing preview images animated using SVG properties. Here on CodyHouse we’ve published a lot of SVG experiments! What’s really powerful with SVG is the possibility to combine path animations with the clipPath element. Add a touch of CSS transformations and you got a spicy recipe! Inspiration: Music player animation by Veronika Lykova. Tool used: Snap.svg Images: Unsplash

Creating the structure

The HTML structure is composed of three unordered lists: a ul.gallery  and a ul.navigation for the slider images and navigation, and a ul.caption for the image captions. Each list item inside the ul.gallery is composed of a .svg-wrapper element wrapping a <svg> containing a  <clipPath> element (used to change the clipping area of the slide image), an <image> element (whose clip-path url attribute is the<clipPath> id), and a <use> element (whose xlink:href attribute is the<clipPath> id) used to create the layer covering the slide images not in the center.
<div class="cd-svg-clipped-slider" data-selected="M780,0H20C8.954,0,0,8.954,0,20v760c0,11.046,8.954,20,20,20h760c11.046,0,20-8.954,20-20V20 C800,8.954,791.046,0,780,0z" data-lateral="M795.796,389.851L410.149,4.204c-5.605-5.605-14.692-5.605-20.297,0L4.204,389.851 c-5.605,5.605-5.605,14.692,0,20.297l385.648,385.648c5.605,5.605,14.692,5.605,20.297,0l385.648-385.648 C801.401,404.544,801.401,395.456,795.796,389.851z">
	<div class="gallery-wrapper">
		<ul class="gallery">
			<li class="left">
				<div class="svg-wrapper">
					<svg viewBox="0 0 800 800">
						<title>Animated SVG</title>
						<defs>
							<clipPath id="cd-image-1">
								<path id="cd-morphing-path-1" d="M795.796,389.851L410.149,4.204c-5.605-5.605-14.692-5.605-20.297,0L4.204,389.851 c-5.605,5.605-5.605,14.692,0,20.297l385.648,385.648c5.605,5.605,14.692,5.605,20.297,0l385.648-385.648 C801.401,404.544,801.401,395.456,795.796,389.851z"/>
							</clipPath>
						</defs>

						<image height='800px' width="800px" clip-path="url(#cd-image-1)" xlink:href="img/img-01.jpg"></image>
						<use xlink:href="#cd-morphing-path-1" class="cover-layer" />
					</svg>
				</div> <!-- .svg-wrapper -->
			</li>

			<li class="selected">
				<div class="svg-wrapper">
					<svg viewBox="0 0 800 800">
						<title>Animated SVG</title>
						<defs>
							<clipPath id="cd-image-2">
								<path id="cd-morphing-path-2" d="M780,0H20C8.954,0,0,8.954,0,20v760c0,11.046,8.954,20,20,20h760c11.046,0,20-8.954,20-20V20 C800,8.954,791.046,0,780,0z"/>
							</clipPath>
						</defs>

						<image height='800px' width="800px" clip-path="url(#cd-image-2)" xlink:href="img/img-02.jpg"></image>
						<use xlink:href="#cd-morphing-path-2" class="cover-layer" />
					</svg>
				</div> <!-- .svg-wrapper -->
			</li>

			<!-- other slides here -->
		</ul>

		<nav>
			<ul class="navigation">
				<li><a href="#0" class="prev">Prev</a></li>
				<li><a href="#0" class="next">Next</a></li>
			</ul>
		</nav>
	</div>

	<ul class="caption">
		<li class="left">Lorem ipsum dolor</li>
		<li class="selected">Consectetur adipisicing elit</li>
		<!-- other captions here -->
	</ul>
</div> <!-- .cd-svg-clipped-slider -->

Adding style

By default, all the list items inside the ul.gallery have a position absolute, an opacity of zero and are moved to the right and scaled down.
.cd-svg-clipped-slider .gallery li {
  /* slider images */
  position: absolute;
  z-index: 1;
  top: 0;
  left: 25%;/* (100% - width)/2 */
  width: 50%;
  height: 100%;
  opacity: 0;
  transform: translateX(75%) scale(0.4);
  transition: opacity .3s, transform .3s ease-in-out;
}
The .selected class is then used to move the selected image back to the center and to scale it up.
.cd-svg-clipped-slider .gallery li.selected {
  /* slide in the center */
  position: relative;
  z-index: 3;
  opacity: 1;
  transform: translateX(0) scale(1);
}
The .left and .right classes are used to show the preview images on both sides of the selected image; the .left class is also used to move an image preview to the left.
.cd-svg-clipped-slider .gallery li.left {
  /* slides on the left */
  transform: translateX(-75%) scale(0.4);
}
.cd-svg-clipped-slider .gallery li.left,
.cd-svg-clipped-slider .gallery li.right {
  /* .right -> slide visible on the right */
  z-index: 2;
  opacity: 1;
}
When a new slide is selected, the <path> element used to clip the slide image is animated to reveal a different portion of the image (the entire image if the slide is the .selected one, or just a section for the .left/.right images). The same classes are also used to control the visibility/position of the image captions. By default, all captions are hidden and moved to the right; the class .selected is used to show the selected caption and move it back to the center, while the .left class is used to hide it and move it to the left.
.cd-svg-clipped-slider .caption li {
  /* slide titles */
  position: absolute;
  z-index: 1;
  top: 0;
  left: 0;
  text-align: center;
  width: 100%;
  transform: translateX(100px);
  opacity: 0;
  transition: opacity .3s, transform .3s ease-in-out;
}
.cd-svg-clipped-slider .caption li.selected {
  /* slide visible in the center */
  z-index: 2;
  position: relative;
  transform: translateX(0);
  opacity: 1;
}
.cd-svg-clipped-slider .caption li.left {
  /* slide hidden on the left */
  transform: translateX(-100px);
}

Events handling

To implement this slider we created a svgClippedSlider object and used the bindEvents method to attach event handlers for the click to the slider navigation.
function svgClippedSlider(element) {
	this.element = element;
	this.slidesGallery = this.element.find('.gallery').children('li');
	this.slidesCaption = this.element.find('.caption').children('li');
	this.slidesNumber = this.slidesGallery.length;
	this.selectedSlide = this.slidesGallery.filter('.selected').index();
	// ....

	this.bindEvents();
}

svgClippedSlider.prototype.bindEvents = function() {
	var self = this;
	//detect click on one of the slides
	this.slidesGallery.on('click', function(event){
		if( !$(this).hasClass('selected') ) {
			//determine new slide index and show it
			var newSlideIndex = ( $(this).hasClass('left') )
				? self.showPrevSlide(self.selectedSlide - 1)
				: self.showNextSlide(self.selectedSlide + 1);
		}
	});
}
The showPrevSlide and showNextSlide methods take care of showing the selected slide; these functions are used to add/remove the proper classes from the slide images and captions, and to animate the 'd' attribute of the <path> element inside the <clipPath> used to clip the slide image.

Product Builder

$
0
0
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));
})

Schedule Template

$
0
0
Schedule Template A simple template that lets you display events on a timeline, as well as organize them in groups (week days, conference rooms etc…) We’ve come across this web component many times: when we check the schedule of a conference, or the timetable of the classes of our gym. From a web designer perspective, it is handy to have a simple, responsive template to use if you ever need to create a schedule table. So we built one!

Creating the structure

The HTML structure is composed of three different elements: a div.timeline for the events timeline(09:00, 09:30, ..), a div.events wrapping the events list and a div.event-modal for the modal window used to provide more details about the selected event.
<div class="cd-schedule">
	<div class="timeline">
		<ul>
			<li><span>09:00</span></li>
			<li><span>09:30</span></li>
			<!-- additional elements here -->
		</ul>
	</div>

	<div class="events">
		<ul>
			<li class="events-group">
				<div class="top-info"><span>Monday</span></div>

				<ul>
					<li class="single-event" data-start="09:30" data-end="10:30" data-content="event-abs-circuit" data-event="event-1">
						<a href="#0">
							<em class="event-name">Abs Circuit</em>
						</a>
					</li>

					<!-- other events here -->
				</ul>
			</li>

			<li class="events-group">
				<div class="top-info"><span>Tuesday</span></div>

				<ul>
					<!-- events here -->
				</ul>
			</li>

			<!-- additional li.events-group here -->
		</ul>
	</div>

	<div class="event-modal">
		<header class="header">
			<div class="content">
				<span class="event-date"></span>
				<h3 class="event-name"></h3>
			</div>

			<div class="header-bg"></div>
		</header>

		<div class="body">
			<div class="event-info"></div>
			<div class="body-bg"></div>
		</div>

		<a href="#0" class="close">Close</a>
	</div>
</div>

Adding style

On small devices (window width less than 800px), all the events inside an .events-group are lined up horizontally: we set a display: flex to the .events-group > ul element and an overflow-x: scroll to make the events scrollable.
.cd-schedule .events .events-group > ul {
  position: relative;
  padding: 0 5%;
  /* force its children to stay on one line */
  display: flex;
  overflow-x: scroll;
  -webkit-overflow-scrolling: touch;
}

.cd-schedule .events .single-event {
  /* force them to stay on one line */
  flex-shrink: 0;
  float: left;
  height: 150px;
  width: 70%;
  max-width: 300px;
}
As for the .event-modal, it is has a fixed position and it is moved to the right outside the viewport. When the user selects an event, the .modal-is-open class is used to translate the .event-modal back into the viewport.
.cd-schedule .event-modal {
  position: fixed;
  z-index: 3;
  top: 0;
  right: 0;
  height: 100%;
  width: 100%;
  visibility: hidden;
  transform: translateX(100%);
  transition: transform .4s, visibility .4s;
}

.cd-schedule.modal-is-open .event-modal {
  /* .modal-is-open class is added as soon as an event is selected */
  transform: translateX(0);
  visibility: visible;
}
On bigger devices, all the events are in absolute position and placed inside a timetable: the top position and the height of each event are evaluated using the data-start and data-end attributes of the event itself and set using JavaScript (more in the Events handling section).
@media only screen and (min-width: 800px) {
  .cd-schedule .events {
    float: left;
    width: 100%;
  }
  .cd-schedule .events .events-group {
    width: 20%;
    float: left;
  }
  .cd-schedule .events .single-event {
    position: absolute;
    z-index: 3;
    /* top position and height will be set using js */
    width: calc(100% + 2px);
    left: -1px;
  }
}
As for the .event-modal, the opening/closing animation is created using jQuery combined with CSS Transitions and Transformations (more in the Events handling section).

Events handling

To implement this event schedule, we created a SchedulePlan object and used the scheduleReset() and initEvents() functions to init the schedule and attach event handlers to the proper elements.
function SchedulePlan( element ) {
  this.element = element;
  this.timeline = this.element.find('.timeline');
  //...

  this.eventsWrapper = this.element.find('.events');
  this.eventsGroup = this.eventsWrapper.find('.events-group');
  this.singleEvents = this.eventsGroup.find('.single-event');
  //..

  this.scheduleReset();
  this.initEvents();
}
On big devices, the scheduleReset() method takes care of placing the events inside the timetable and set their height. To evaluate the height, for example, we calculate the duration of the event (data-end minus data-start), divide it by the 'eventUnit' (in our case it's 30 minutes) and then multiply it by the height of 'timeline unit' (in our case, 50px).
var self = this;
this.singleEvents.each(function(){
  //place each event in the grid -> need to set top position and height
  var start = getScheduleTimestamp($(this).attr('data-start')), //getScheduleTimestamp converts hh:mm to timestamp
    duration = getScheduleTimestamp($(this).attr('data-end')) - start;

  var eventTop = self.eventUnitHeight*(start - self.timelineStart)/self.timelineUnitDuration,
    eventHeight = self.eventUnitHeight*duration/self.timelineUnitDuration;

  $(this).css({
    top: (eventTop -1) +'px',
    height: (eventHeight+1)+'px'
  });
});
When the user selects an event, the jQuery load() function is used to load the content of the event just selected (its data-content is used to determine the file content to be loaded). In addition to that, on big devices, the .event-modal is animated to show the event content. First, the .event-modal is placed on top of the selected event and its height and width are changed to be equal to the ones of the selected event; then the .header-bg and .body-bg elements are scaled up to create the morphing animation; at the end of this animation, the modal content is revealed.
SchedulePlan.prototype.openModal = function(event) {
	var self = this;
	var mq = self.mq();
	this.animating = true;

	//update event name and time
	this.modalHeader.find('.event-name').text(event.find('.event-name').text());
	this.modalHeader.find('.event-date').text(event.find('.event-date').text());
	this.modal.attr('data-event', event.parent().attr('data-event'));

	//update event content
	this.modalBody.find('.event-info').load(event.parent().attr('data-content')+'.html .event-info > *', function(data){
		//once the event content has been loaded
		self.element.addClass('content-loaded');
	});

	this.element.addClass('modal-is-open');

	if( mq == 'mobile' ) {
		self.modal.one(transitionEnd, function(){
			self.modal.off(transitionEnd);
			self.animating = false;
		});
	} else {
		//change modal height/width and translate it
		self.modal.css({
			top: eventTop+'px', //this is the selected event top position
			left: eventLeft+'px', //this is the selected event left position
			height: modalHeight+'px', //this is the modal final height
			width: modalWidth+'px', //this is the modal final width
		});
		transformElement(self.modal, 'translateY('+modalTranslateY+'px) translateX('+modalTranslateX+'px)');

		//set modalHeader width
		self.modalHeader.css({
			width: eventWidth+'px',  //this is the selected event width
		});
		//set modalBody left margin
		self.modalBody.css({
			marginLeft: eventWidth+'px',
		});

		//change modalBodyBg height/width and scale it
		self.modalBodyBg.css({
			height: eventHeight+'px',
			width: '1px',
		});
		transformElement(self.modalBodyBg, 'scaleY('+HeaderBgScaleY+') scaleX('+BodyBgScaleX+')');

		//change modal modalHeaderBg height/width and scale it
		self.modalHeaderBg.css({
			height: eventHeight+'px',
			width: eventWidth+'px',
		});
		transformElement(self.modalHeaderBg, 'scaleY('+HeaderBgScaleY+')');

		self.modalHeaderBg.one(transitionEnd, function(){
			//wait for the  end of the modalHeaderBg transformation and show the modal content
			self.modalHeaderBg.off(transitionEnd);
			self.animating = false;
			self.element.addClass('animation-completed');
		});
	}
};
One note: we implemented a simple load() function to upload the new html content, but you may wanna replace it with a $.ajax call in order to handle errors, beforeSend request etc. according to your project.
Viewing all 26 articles
Browse latest View live