CodyHouse » Search Results » svg 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 Image Mask Effect https://codyhouse.co/gem/image-mask-effect/ https://codyhouse.co/gem/image-mask-effect/#comments Wed, 15 Feb 2017 12:13:59 +0000 https://codyhouse.co/?post_type=gem&p=19101 image-mask-effect An immersive transition effect powered by image masks and CSS transforms. We’ve been publishing in our library some nice mask effects powered by SVG properties. This time we took advantage of the PNG transparencies to zoom through the mask layer, into a project background image. If you want to change the color of the .pgn masks, you can easily do it in Photoshop (or any other graphic tool) by applying a color overlay to the whole image layer. If you plan to create your own masks, please note that this effect works only if there’s an empty space in the very center of the mask. Inspiration: Offsite Homepage Animation by Hrvoje Grubisic.

Creating the structure

The HTML structure is composed of a list of <section>s wrapped in a .cd-image-mask-effect element. Each <section> contains a div.featured-image (project image), a div.mask (image mask) and a div.cd-project-info for the project content.
<section class="project-1 cd-project-mask">
	<h1>Project Name</h1>
	<div class="featured-image"></div>
	<div class="mask">
		<img src="img/mask-01.png" alt="mask">
		<span class="mask-border mask-border-top"></span>
		<span class="mask-border mask-border-bottom"></span>
		<span class="mask-border mask-border-left"></span>
		<span class="mask-border mask-border-right"></span>
	</div>

	<a href="#0" class="project-trigger">Explore Project</a>

	<a href="#0" class="cd-scroll cd-img-replace">Scroll down</a>

	<div class="cd-project-info" data-url="project-1">
		<!-- content loaded using js -->
	</div>

	<a href="#0" class="project-close cd-img-replace">Close Project</a>
</section> <!-- .cd-project-mask -->

<section class="project-2 cd-project-mask">
	<!-- content here -->
</section>

<!-- other sections here -->
The project content is not included in the HTML but is loaded using JavaScript.

Adding style

Each .cd-project-mask has a height of 100vh (viewport height) and a width of 100%; the project image is set as background-image of the .featured-image element, while the mask image is wrapped inside the .mask element. Four .mask-border elements have been used to create a frame around the image mask to make sure the project featured image is not visible outside the mask (we used <span> elements rather than pseudo elements because their behaviour was buggy on Safari 9).
.cd-project-mask {
  position: relative;
  height: 100vh;
  width: 100%;
  overflow: hidden;
}

.cd-project-mask .featured-image {
    /* project intro image */
    position: absolute;
    left: 50%;
    top: 50%;
    bottom: auto;
    right: auto;
    transform: translateX(-50%) translateY(-50%);
  	height: 100%;
  	width: 100%;
  	background: url(../img/img-01.jpg) no-repeat center center;
  	background-size: cover;
}

.cd-project-mask .mask {
  position: absolute;
  left: 50%;
  top: 50%;
  bottom: auto;
  right: auto;
  transform: translateX(-50%) translateY(-50%);
  width: 300px;
  height: 300px;
}

.cd-project-mask .mask .mask-border {
  /* this is used to create a frame around the mask */
  position: absolute;
}

.cd-project-mask .mask .mask-border-top,
.cd-project-mask .mask .mask-border-bottom {
    /* this is used to create a frame around the mask */
    height: calc(50vh - 150px + 10px);
    width: 100vw;
    left: 50%;
    right: auto;
    transform: translateX(-50%);
}

.cd-project-mask .mask .mask-border-top {
    bottom: calc(100% - 10px);
}

.cd-project-mask .mask .mask-border-bottom {
    top: calc(100% - 10px);
}

.cd-project-mask .mask .mask-border-left,
.cd-project-mask .mask .mask-border-right {
  	/* this is used to create a frame around the mask */
	height: 100vh;
	width: calc(50vw - 150px + 10px);
	top: 50%;
	bottom: auto;
    transform: translateY(-50%);
}

.cd-project-mask .mask .mask-border-left {
  left: calc(100% - 10px);
}

.cd-project-mask .mask .mask-border-right {
  right: calc(100% - 10px);
}
When the user selects a project, the class .project-view (added to the wrapper .cd-image-mask-effect) is used to hide all the other projects. The .mask element is then scaled up to reveal the project featured image and the project content is loaded (more in the Events handling section).
.project-view .cd-project-mask:not(.project-selected) {
   /* the project-view class is added to the .cd-image-mask-effect element when a project is selected - hide all not selected projects */
   position: absolute;
   top: 0;
   left: 0;
   opacity: 0;
   visibility: hidden;
}

Events handling

To implement this image mask effect, we created a ProjectMask object and used the initProject method to attach the proper event handlers.
function ProjectMask( element ) {
	this.element = element;
	this.projectTrigger = this.element.find('.project-trigger');
	this.projectClose = this.element.find('.project-close'); 
	this.projectTitle = this.element.find('h1');
	this.projectMask = this.element.find('.mask');
	//...
	this.initProject();
}

var revealingProjects = $('.cd-project-mask');
var objProjectMasks = [];

if( revealingProjects.length > 0 ) {
	revealingProjects.each(function(){
		//create ProjectMask objects
		objProjectMasks.push(new ProjectMask($(this)));
	});
}
When the user selects a project, the revealProject method is used to scale up the mask image while the uploadContent method takes care of loading the project content (using the load() function) and adding the new page to the window.history (using the pushState() method).
ProjectMask.prototype.initProject = function() {
	var self = this;

	//open the project
	this.projectTrigger.on('click', function(event){
		event.preventDefault();
		if( !self.animating ) {
			self.animating = true;
			//upload project content
			self.uploadContent();
			//show project content and scale up mask
			self.revealProject();
		}
	});

	//...
};

ProjectMask.prototype.revealProject = function() {
	var self = this;
	//get mask scale value
	self.updateMaskScale();
	//scale up mask and animate project title
	self.projectTitle.attr('style', 'opacity: 0;');
	self.projectMask.css('transform', 'translateX(-50%) translateY(-50%) scale('+self.maskScaleValue+')').one(transitionEnd, function(){
		self.element.addClass('center-title');
		self.projectTitle.attr('style', '');
		self.animating = false;
	});

	//hide the other sections
	self.element.addClass('project-selected content-visible').parent('.cd-image-mask-effect').addClass('project-view');
}

ProjectMask.prototype.uploadContent = function(){
	var self = this;
	//if content has not been loaded -> load it
	if( self.projectContent.find('.content-wrapper').length == 0 ) self.projectContent.load(self.projectContentUrl+'.html .cd-project-info > *');
	
	if( self.projectContentUrl+'.html'!=window.location ){
        //add the new page to the window.history
        window.history.pushState({path: self.projectContentUrl+'.html'},'',self.projectContentUrl+'.html');
    }
}
 ]]>
https://codyhouse.co/gem/image-mask-effect/feed/ 11
Immersive Video Template https://codyhouse.co/gem/immersive-video-template/ https://codyhouse.co/gem/immersive-video-template/#comments Tue, 20 Dec 2016 11:53:39 +0000 https://codyhouse.co/?post_type=gem&p=18351 Immersive Video Template A full-screen video presentation, that is resized and animated to become the content of a mobile device. Lately, we’ve come across a couple of websites using full-screen videos as a way to “dramatically” introduce a product or a feature of an app. We tried to create something similar, in the form of a simple application template. With the help of some CSS tricks (mostly CSS transforms and animations), we’ve created a video introduction that turns into content for a mobile device. Some of the tricky parts were about picking the right assets (we’ve created this video that introduces some features of a photo editing app) and handling the loading animation. We’re aware that showing a loader right away is not a great experience. Therefore, before using this template you should consider 1) whether the video would be a great selling point or not, and 2) whether you expect many returning visitors, or the goal is just to let the user download the app and (almost) never come back. If the answers to these questions are 1) YES and 2) No, then this template will come in handy for you ;) Video footage: pexels.com Inspiration: Landing Page by Cuberto Icons: Nucleo

Creating the structure

The HTML structure is composed of two main elements: a div.product-intro (product title, action button, ..) and a div.product-preview for the image/video preview of the product.
<div class="cd-immersive-video">
	<div class="intro-wrapper">
		<div class="product-intro">
			<div>
				<h1 class="item item-1">Immersive Video Template</h1>
				
				<p class="item item-2">
					<!-- product description here -->
				</p>

				<a href="#0" class="cd-btn item item-3">Download &amp; Article</a>
			</div>
		</div> <!-- .product-intro -->

		<div class="product-preview no-transition">
			<div class="device-frame">
				<img src="assets/img/mobile-frame.svg" alt="Device Frame">
			</div>

			<div class="product-image">
				<img src="assets/img/video-fallback.jpg" alt="Product Preview">
			</div>

			<div class="product-video" data-video="assets/video/video.mp4">
				<div class="video-wrapper">
					<!-- video will be insereted here using js -->
				</div>
			</div>
		</div> <!-- .product-preview -->
	</div> 

	<div class="cd-loader"></div>
</div> <!-- .cd-immersive-video -->
The video is not directly inserted in the HTML but it's loaded using JavaScript.

Adding style

On small devices (viewport width less than 800px), the css is pretty straightforward: both the div.product-intro and the div.product-preview are initially hidden ( opacity: 0 ) and then revealed using the cd-item-move-up animation.
@keyframes cd-item-move-up {
  	0% {
    	opacity: 0;
    	transform: translateY(50px);
  	}
  	100% {
   		opacity: 1;
    	transform: translateY(0);
  	}
}
The product preview video is not loaded on these devices while an image is used as a preview of the product (div.product-image > img). On bigger devices, the product intro and preview are initially hidden; once the preview video has been loaded (more in the Events handling section), the .video-is-loaded class is used to reveal the content and trigger the video animation.
@media only screen and (min-width: 800px) {
  .cd-immersive-video .intro-wrapper {
    /* while loading the video - hide the content */
    visibility: hidden;
    opacity: 0;
    transition: opacity 1.5s, visibility 1.5s;
  }
  .cd-immersive-video.video-is-loaded .intro-wrapper {
    /* video has been loaded - reveal content */
    visibility: visible;
    opacity: 1;
  }
}
For the video animation: the div.product-video is initially scaled-up (using JavaScript) to cover the entire viewport. Once the video has been played for around 1s, the div.product-video is scaled down and translated to the right. The div.product-intro is then animated using the .animate-content class: each item of the intro (h1, p..) is revealed using the cd-item-slide-in animation with a different animation-delay.
@media only screen and (min-width: 800px) {
  .cd-immersive-video .product-intro .item {
    opacity: 0;
  }
  .cd-immersive-video .product-intro.animate-content .item {
    animation: cd-item-slide-in .4s both;
  }
  .cd-immersive-video .product-intro.animate-content .item-1 {
    animation-delay: .1s;
  }
  .cd-immersive-video .product-intro.animate-content .item-2 {
    animation-delay: .2s;
  }
  .cd-immersive-video .product-intro.animate-content .item-3 {
    animation-delay: .3s;
  }
}

Events handling

On big devices, we use jQuery to load the product preview video and insert it into the HTML structure. We used the canplaythrough event to detect if the video is ready to be played; we also added a check to the readyState of the video  (if the video is cached the canplaythrough event may not be detected).
if( productVideo.is(':visible') ) { //productVideo = $('.product-video');
	//the video src is the data-video of productVideo
	var	video = $('<video><source src="'+productVideo.data('video')+'" type="video/mp4" />Sorry, your browser does not support HTML5 video.</video>');
	
	//check if the canplaythrough event occurs - video is ready to be played
	video.on('canplaythrough', function() {
		video.off('canplaythrough').trigger('readyToPlay');
	});
	// if video is in cache 'canplaythrough' won't be triggered 
	if (video.get(0).readyState > 3) {
		video.off('canplaythrough').trigger('readyToPlay');
	}
}
Once the video is ready, we insert it into the HTML, hide the loader and then trigger the video animation.
video.on('readyToPlay', function(){
	//video id ready to play
	video.appendTo(productVideo.find('.video-wrapper'));

	//wait for the end of an animation iteraction and reveal the video
	loader.one(animationIteration, function() {
        loader.addClass('no-animation').off(animationIteration);
		//makes sure the transition is applied (when using the scale-down class)
        //https://css-tricks.com/restart-css-animation/
        void loader.get(0).offsetWidth; 
        loader.addClass('scale-down');
        loader.on(transitionEnd, function(){
        	loader.off(transitionEnd);
        	immersiveVideoWrapper.trigger('startAnimation'); //animate div.product-video
        });
    });
});
]]>
https://codyhouse.co/gem/immersive-video-template/feed/ 4
Clipped SVG Slider https://codyhouse.co/gem/clipped-svg-slider/ https://codyhouse.co/gem/clipped-svg-slider/#comments Wed, 27 Jul 2016 10:03:07 +0000 https://codyhouse.co/?post_type=gem&p=15333 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.]]>
https://codyhouse.co/gem/clipped-svg-slider/feed/ 5
Auto-Hiding Navigation https://codyhouse.co/gem/auto-hiding-navigation/ https://codyhouse.co/gem/auto-hiding-navigation/#comments Wed, 13 Jul 2016 10:22:03 +0000 https://codyhouse.co/?post_type=gem&p=14754 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.]]>
https://codyhouse.co/gem/auto-hiding-navigation/feed/ 47
Radial SVG Slider https://codyhouse.co/gem/radial-svg-slider/ https://codyhouse.co/gem/radial-svg-slider/#comments Thu, 02 Jun 2016 13:02:33 +0000 https://codyhouse.co/?post_type=gem&p=13475 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.]]>
https://codyhouse.co/gem/radial-svg-slider/feed/ 21
360 Degrees Product Viewer https://codyhouse.co/gem/360-degrees-product-viewer/ https://codyhouse.co/gem/360-degrees-product-viewer/#comments Wed, 13 Apr 2016 10:28:09 +0000 https://codyhouse.co/?post_type=gem&p=9621 360 Degrees Product Viewer A simple, interactive resource that can be used to provide a virtual tour of your product. In e-commerce design, one of the main goals is to fill the gap between product and user. This is particularly relevant for high-priced goods. Hence, the importance to integrate interactive tools, to provide the user a way to “virtually experience” the product. Today’s resource is a simple, interactive resource that can be used to show a virtual tour of the product. The idea behind the snippet is to use an image sprite and link the dragging to a specific frame of that image. You can use it to show the exterior of a technology gadget (or a car, like in our demo!), or, in general, to create fancy product animations. Photo credits: Alfa Romeo.

Creating the structure

The HTML structure is composed of two main elements: a figure.product-viewer for the image sprite and the product preview image, and a div.cd-product-viewer-handle for the viewer handle.
<div class="cd-product-viewer-wrapper" data-frame="16" data-friction="0.33">
	<div>
		<figure class="product-viewer">
			<img src="img/product-loading.jpg" alt="Product Preview">
			<div class="product-sprite" data-image="img/product.png"></div>
		</figure> <!-- .product-viewer -->

		<div class="cd-product-viewer-handle">
			<span class="fill"></span>
			<span class="handle">Handle</span>
		</div>
	</div> <!-- .cd-product-viewer-handle -->
</div> <!-- .cd-product-viewer-wrapper -->
The data-frame attribute of the div.cd-product-viewer-wrapper specifies the number of frames the image sprite is composed of, while the data-friction specifies the friction while dragging on the image (it has to be greater than zero).

Adding style

The <img> element is visible only at the beginning, while the image sprite is still loading, and is used to give the proper dimensions to the figure.product-viewer element. As for the div.product-sprite, it has an absolute position, a height of 100% and width of 1600% (our image sprite is composed of 16 frames) and is hidden by default. The .loaded class is then used to show the div.product-sprite once the image sprite has been loaded:
.cd-product-viewer-wrapper .product-viewer {
  position: relative;
  overflow: hidden;
}
.cd-product-viewer-wrapper img {
  /* this is the image visible before the image sprite is loaded */
  display: block;
  position: relative;
  z-index: 1;
}
.cd-product-viewer-wrapper .product-sprite {
  position: absolute;
  z-index: 2;
  top: 0;
  left: 0;
  height: 100%;
  /* our image sprite is composed of 16 frames */
  width: 1600%;
  background: url(../img/product.png) no-repeat center center;
  background-size: 100%;
  opacity: 0;
  transition: opacity 0.3s;
}
.cd-product-viewer-wrapper.loaded .product-sprite {
  /* image sprite has been loaded */
  opacity: 1;
}
When the user drags the span.handle or the product image, we change the div.product-sprite translateX value to show a different image frame (using JavaScript). Note: the frames composing your image sprite should have the same aspect ratio of the product preview image. The handle loading effect is achieved by changing the scaleX value of the span.fill element (using JavaScript); once the image sprite has been loaded, the span.fill is hidden and the span.handle is shown:
.cd-product-viewer-handle {
  position: relative;
  z-index: 2;
  width: 60%;
  max-width: 300px;
  height: 4px;
  background: #4d4d4d;
}
.cd-product-viewer-handle .fill {
  /* this is used to create the loading fill effect */
  position: absolute;
  z-index: 1;
  left: 0;
  top: 0;
  height: 100%;
  width: 100%;
  border-radius: inherit;
  background: #b54240;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.5s;
}
.loaded .cd-product-viewer-handle .fill {
  /* image sprite has been loaded */
  opacity: 0;
}
.cd-product-viewer-handle .handle {
  position: absolute;
  z-index: 2;
  display: inline-block;
  height: 44px;
  width: 44px;
  left: 0;
  top: -20px;
  background: #b54240 url(../img/cd-arrows.svg) no-repeat center center;
  border-radius: 50%;
  transform: translateX(-50%) scale(0);
}
.loaded .cd-product-viewer-handle .handle {
  /* image sprite has been loaded */
  transform: translateX(-50%) scale(1);
  animation: cd-bounce 0.3s 0.3s;
  animation-fill-mode: both;
}
@keyframes cd-bounce {
  0% {
    transform: translateX(-50%) scale(0);
  }
  60% {
    transform: translateX(-50%) scale(1.1);
  }
  100% {
    transform: translateX(-50%) scale(1);
  }
}

Events handling

To implement the product viewer, we created a productViewer object and used the loadFrames method to check whether the image sprite has been loaded:
var productViewer = function(element) {
	this.element = element;
	this.handleContainer = this.element.find('.cd-product-viewer-handle');
	this.handleFill = this.handleContainer.children('.fill');
	//...
	this.frames = this.element.data('frame');
	//increase this value to increase the friction while dragging on the image - it has to be bigger than zero
	this.friction = this.element.data('friction');
	this.visibleFrame = 0;
	this.loaded = false;
	//...
	this.loadFrames();
} 

productViewer.prototype.loadFrames = function() {
	var self = this,
		imageUrl = this.slideShow.data('image');
	//you need this to check if the image sprite has been loaded
	$('<img/>').attr('src', imageUrl).load(function() {
		self.loaded = true;
	});

	this.loading('0.5'); //triggers loading animation
}

var productToursWrapper = $('.cd-product-viewer-wrapper');
productToursWrapper.each(function(){
	new productViewer($(this));
});
Once the image sprite has been loaded, we attach an event handler for the mousedown/mousemove/mouseup events to the proper elements:
if( self.loaded ){
	//sprite image has been loaded
	self.element.addClass('loaded');
	self.dragImage();
	self.dragHandle();
	//..
} else {
	//...
}
For this effect to work on touch devices, we used the vmousedown/vmousemove/vmouseup events provided by the jQuery mobile framework.]]>
https://codyhouse.co/gem/360-degrees-product-viewer/feed/ 32
Advanced Search Form https://codyhouse.co/gem/advanced-search-form/ https://codyhouse.co/gem/advanced-search-form/#comments Thu, 04 Feb 2016 11:03:06 +0000 https://codyhouse.co/?post_type=gem&p=8084 advanced-search A search form with advanced filtering options and quick link suggestions. Getting the search experience right is never an easy task. The starting point is always the search form, which, in most cases, consists only of an input field plus a submit button. The search results page can be tricky to design, in particular if you have different content categories. For big websites and online stores, what is crucial though is to try to anticipate a user’s move. We have to take into account that often our users are not clear about where to find specific information on our website. In those cases they tend to turn to the search form. Providing filtering options and quick links upfront is a way to narrow the search experience to what the user is really interested in, as well as a way to build simpler, more focused search result pages. Here is a quick animation that shows our advanced search form in action: advanced-search-animation

Creating the structure

The HTML structure is composed by three main elements: a <header> element, wrapping the main navigation, a div.cd-main-search for the search form and a main.cd-main-content for the page main content.
<header class="cd-main-header animate-search">
	<div class="cd-logo"><a href="#0"><img src="img/cd-logo.svg" alt="Logo"></a></div>

	<nav class="cd-main-nav-wrapper">
		<a href="#search" class="cd-search-trigger cd-text-replace">Search</a>
		
		<ul class="cd-main-nav">
			<li><a href="#0">Products</a></li>
			<!-- additional navigation items -->
		</ul>
	</nav>

	<a href="#0" class="cd-nav-trigger cd-text-replace">Menu<span></span></a>
</header>

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

<div id="search" class="cd-main-search">
	<form>
		<input type="search" placeholder="Search...">

		<div class="cd-select">
			<span>in</span>
			<select name="select-category">
				<option value="all-categories">all Categories</option>
				<!-- additional options here -->
			</select>
			<span class="selected-value">all Categories</span>
		</div>
	</form>

	<div class="cd-search-suggestions">
		<div class="news">
			<h3>News</h3>
			<ul>
				<li>
					<a class="image-wrapper" href="#0"><img src="img/placeholder.png" alt="News image"></a>
					<h4><a class="cd-nowrap" href="#0">Lorem ipsum dolor sit amet, consectetur adipisicing elit.</a></h4>
					<time datetime="2016-01-12">Feb 03, 2016</time>
				</li>

				<!-- additional news here -->
			</ul>
		</div> <!-- .news -->

		<div class="quick-links">
			<h3>Quick Links</h3>
			<ul>
				<li><a href="#0">Find a store</a></li>
				<!-- additional quick links here -->
			</ul>
		</div>
	</div> <!-- .cd-search-suggestions -->

	<a href="#0" class="close cd-text-replace">Close Form</a>
</div> <!-- .cd-main-search -->

Adding style

On small devices (viewport width smaller than 1024px), the main navigation and the search form are on the right side, hidden by default; when a user clicks the menu icon, the <main> and <header> elements translate to the left (nav-is-visible class is applied) to reveal the navigation.
.cd-main-header, .cd-main-content {
  position: relative;
  transition: transform 0.3s;
}
.cd-main-header.nav-is-visible, .cd-main-content.nav-is-visible {
  transform: translateX(-260px);
}
On bigger devices, the search form is on top of the main navigation, hidden by default. When a user clicks the .cd-search-trigger element, the .is-visible class is used to reveal the form.
@media only screen and (min-width: 1024px) {
  .cd-main-search {
    position: absolute;
    z-index: 2;
    top: 0;
    left: 0;
    height: 100%;
    width: 100%;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s, visibility 0.3s;
  }
  .cd-main-search.is-visible {
    /* search form open */
    opacity: 1;
    visibility: visible;
  }
}
To trigger the search form animation, we use the .animate-search class added to the <header> element. This class triggers two different CSS3 Animations: cd-slide-in (for the search form) and cd-3d-rotation (for the suggestions dropdown).
@media only screen and (min-width: 1024px) {
  .animate-search .cd-main-search.is-visible {
    /* trigger search form animation if <header> has the .animate-search class */
    animation: cd-slide-in 0.3s;
  }
  .animate-search .is-visible .cd-search-suggestions {
    /* trigger the dropdown animation if <header> has the .animate-search class */
    transform-origin: center top;
    animation: cd-3d-rotation 0.5s 0.3s;
    animation-fill-mode: backwards;
  }
}
@keyframes cd-slide-in {
  0% {
    transform: translateY(-100%);
  }
  100% {
    transform: translateY(0);
  }
}

@keyframes cd-3d-rotation {
  0% {
    transform: perspective(1000px) rotateX(-90deg);
  }
  100% {
    transform: perspective(1000px) translateY(0);
  }
}
If you prefer a basic fade-in effect (rather than animating the search form), you can remove the .animate-search class from the <header> element. About the category selection: to make sure the div.cd-select width changes according to the option the user chooses, the <select> element is in absolute position (so it doesn't take space), while a span.selected-value is used to show the option selected (its text is changed  when the user selects a new option using jQuery).
@media only screen and (min-width: 1024px) {
  .cd-main-search .cd-select {
    position: absolute;
    right: 0;
    overflow: hidden;
  }
  .cd-main-search select {
    /* the <select> element is not visible - it is covered by the .selected-value element */
    position: absolute;
    right: 0;
    opacity: 0;
    color: transparent;
  }
  .cd-main-search .selected-value {
    color: #ffffff;
    pointer-events: none;
  }
  .cd-main-search select, .cd-main-search .selected-value {
    padding: 0.5em 1.7em 0.5em .3em;
    font-size: 1.4rem;
    border-radius: 3px;
  }
}

Events handling

In the starting HTML structure, the navigation is inside the <header>. On small devices, 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 do that. We do the same for the div.cd-main-search: by default, it's outside the main navigation, while on small devices we move it inside the nav.cd-main-nav-wrapper element.
var navigationWrapper = $('.cd-main-nav-wrapper'),
	navigation = navigationWrapper.children('.cd-main-nav'),
	searchForm = $('.cd-main-search'),
	navigationTrigger = $('.cd-nav-trigger'),
	mainHeader = $('.cd-main-header');

function moveNavigation(){
	var screenSize = checkWindowWidth(); //returns 'mobile' or 'desktop'
    if ( screenSize == 'desktop' && (navigationTrigger.siblings('.cd-main-search').length == 0) ) {
    	//desktop screen - insert navigation and search form inside <header>
    	searchForm.detach().insertBefore(navigationTrigger);
		navigationWrapper.detach().insertBefore(searchForm).find('.cd-serch-wrapper').remove();
	} else if( screenSize == 'mobile' && !(mainHeader.children('.cd-main-nav-wrapper').length == 0)) {
		//mobile screen - move navigation and search form after .cd-main-content element
		navigationWrapper.detach().insertAfter('.cd-main-content');
		var newListItem = $('<li class="cd-serch-wrapper"></li>');
		searchForm.detach().appendTo(newListItem);
		newListItem.appendTo(navigation);
	}
}
Besides, we used jQuery to detect click events and add/remove classes accordingly and to change the span.selected-value text when user selects a different option from the <select> dropdown.]]>
https://codyhouse.co/gem/advanced-search-form/feed/ 15
Vertical Fixed Navigation #2 https://codyhouse.co/gem/vertical-fixed-navigation-2/ https://codyhouse.co/gem/vertical-fixed-navigation-2/#comments Thu, 07 Jan 2016 12:25:27 +0000 https://codyhouse.co/?post_type=gem&p=6989 vertical-fixed-navigation-2 A smart vertical navigation, with round indicators that turn into labelled icons when the user interacts with them. Our first concept of vertical fixed navigation is one of our most popular resources. This time we tried to push this concept a little further. The basic idea behind putting round indicators on the side of a web page, is to give a hint to the user about the number of sections she/he can go through. We think of each dot as a content chapter, with its own title. Usually users have to hover over a dot to access the title. In an attempt to simplify this pattern, we decided to transform the dots when the user interacts with them, by scaling them up and showing an icon + label. Users don’t need to select a specific dot/item, but just move to the side, thus showing their willingness to access the navigation. Here is a quick preview of the final result (created using After Effects): v-nav-animation

Creating the structure

Our navigation is an unordered list wrapped in a nav.cd-vertical-nav. A button.cd-nav-trigger is used to open the navigation on small devices. Besides, a section.cd-section has been created for each navigation item.
<nav class="cd-vertical-nav">
	<ul>
		<li><a href="#section1" class="active"><span class="label">Intro</span></a></li>
		<li><a href="#section2"><span class="label">Events</span></a></li>
		<!-- additional navigation items here -->
	</ul>
</nav><!-- .cd-vertical-nav -->

<button class="cd-nav-trigger cd-image-replace">Open navigation<span aria-hidden="true"></span></button>

<section id="section1" class="cd-section">
	<div class="content-wrapper">
		<h1>Vertical Fixed Navigation #2</h1>
		<a href="#section2" class="cd-scroll-down cd-image-replace">scroll down</a>
	</div>
</section><!-- cd-section -->

<section id="section2" class="cd-section">
	<div class="content-wrapper">
		<!-- section content here -->
	</div>
</section><!-- cd-section -->

<!-- additional sections here -->

Adding style

On small devices (viewport width smaller than 800px), we set a position: fixed for the .cd-nav-trigger and <nav> elements and  placed them at the bottom-right corner of the page; we then scale down the navigation, using the bottom-right corner as transform origin. When user clicks on the .cd-nav-trigger element, we give the .open class to the navigation to change its scale value from 0 to 1 , with a CSS3 transition to achieve a smooth animation.
.cd-nav-trigger {
  display: block;
  position: fixed;
  z-index: 2;
  bottom: 30px;
  right: 5%;
}

.cd-vertical-nav {
  position: fixed;
  z-index: 1;
  right: 5%;
  bottom: 30px;
  transform: scale(0);
  transform-origin: right bottom;
  transition: transform 0.2s;
}
.cd-vertical-nav.open {
  transform: scale(1);
}
On bigger devices, we use Modernizr to detect touch and no-touch devices (using.touch and .no-touch classes). On touch devices, the lateral navigation items (labels and icons) are visible by default, while on no-touch devices they are shown when the user hovers over the lateral navigation. We set a fixed height and width for the <nav> element, and place it on the right side of the viewport. We use its ::before pseudo-element to create the navigation background; on no-touch devices only, the ::before element is, by default, translated to the right (outside the viewport) and is moved back to its original position when the user hovers over the navigation. The same happens for the span.label elements.
@media only screen and (min-width: 800px) {
  .cd-vertical-nav {
    right: 0;
    top: 0;
    height: 100vh;
    width: 90px;
  }
  .cd-vertical-nav::before {
    /* this is the navigation background */
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.8);
    transform: translateX(100%);
    transition: transform 0.4s;
  }
  .no-touch .cd-vertical-nav:hover::before, 
  .touch .cd-vertical-nav::before {
    transform: translateX(0);
  }
  .cd-vertical-nav .label {
    display: block;
    transform: translateX(100%);
    transition: transform 0.4s;
  }
  .no-touch .cd-vertical-nav:hover .label, 
  .touch .cd-vertical-nav .label {
    transform: translateX(0);
  }
}
To create the navigation item icons and dots, we use, respectively, the ::after and ::before pseudo-elements of the navigation anchor elements (<a>). On no-touch devices only, the ::after and ::before are scaled down by default, and then scaled back up when the user hovers over the navigation.
@media only screen and (min-width: 800px) {
  .cd-vertical-nav a {
    position: relative;
    padding: 3em 0 0;
    margin: 1.4em auto;
  }
  .cd-vertical-nav a::before, 
  .cd-vertical-nav a::after {
    /* used to create the filled circle and the background icon */
    content: '';
    position: absolute;
    left: 50%;
    transition: transform 0.4s 0s;
  }
  .cd-vertical-nav a::before {
    /* filled circle */
    top: 0;
    height: 32px;
    width: 32px;
    border-radius: 50%;
    background: #eaf2e3;
    transform: translateX(-50%) scale(0.25);
  }
  .cd-vertical-nav a::after {
    /* icon */
    top: 8px;
    height: 16px;
    width: 16px;
    background: url(../img/cd-nav-icons.svg) no-repeat;
    transform: translateX(-50%) scale(0);
  }
  .no-touch .cd-vertical-nav:hover a::before, 
  .no-touch .cd-vertical-nav:hover a::after, 
  .touch .cd-vertical-nav li:nth-of-type(n) a::before, 
  .touch .cd-vertical-nav li:nth-of-type(n) a::after {
    transform: translateX(-50%) scale(1);
  }
}
Now the tricky part: when the navigation dots are scaled down, they are too distant one from the other. We can reduce this distance translating them along the Y axis. Let's start from the central dots (in our case, the second and the third); we want to translate down the second one (so we have to use a positive translate value), while we want to translate up the third one (so we have to use a negative translate value). In our case we have:
.cd-vertical-nav li:nth-of-type(2) a::after {
    transform: translateX(-50%) translateY(1.5em) scale(0);
  }
  .cd-vertical-nav li:nth-of-type(2) a::before {
    transform: translateX(-50%) translateY(1.5em) scale(0.25);
  }
  .cd-vertical-nav li:nth-of-type(3) a::after {
    transform: translateX(-50%) translateY(-1.5em) scale(0);
  }
  .cd-vertical-nav li:nth-of-type(3) a::before {
    transform: translateX(-50%) translateY(-1.5em) scale(0.25);
  }
Then, the translate value for the first dot is gonna be three times the one of the second dot, and the same for the fourth one (three times the translate value of the third dot).
.cd-vertical-nav li:first-of-type a::after {
    transform: translateX(-50%) translateY(4.5em) scale(0);
  }
  .cd-vertical-nav li:first-of-type a::before {
    transform: translateX(-50%) translateY(4.5em) scale(0.25);
  }
  .cd-vertical-nav li:nth-of-type(4) a::after {
    transform: translateX(-50%) translateY(-4.5em) scale(0);
  }
  .cd-vertical-nav li:nth-of-type(4) a::before {
    transform: translateX(-50%) translateY(-4.5em) scale(0.25);
  }
If you have a different number of navigation items, you have to change these translate values accordingly. For example, if you have six items, starting again from the central dots (in this case, the third and the fourth), you can assign them a translateY value of 1.5 em/-1.5em; then to the second and the fifth a translateY value of 4.5em/-4.5em (3*1.5), and finally to the first and sixth a translateY value of 7.5em/-7.5em (5*1.5). If you have an odd number of items, let's say 5, you do not translate the central one (in this case the third one). You then assign a translateY value of 3em/-3em(2*1.5) to the second and the fourth dots, and finally a translateY value of 6em/-6em(4*1.5) to the first and fifth dots.

Events handling

When user scrolls through the sections, the updateSections() function evaluates which section is currently being viewed and assigns the .active class to the corresponding navigation item. Besides, we listen to the click event on the button.cd-nav-trigger to open/close the navigation on small devices.]]>
https://codyhouse.co/gem/vertical-fixed-navigation-2/feed/ 36