Vertical Showcase Slider with jQuery and CSS Transitions

A tutorial on how to create a responsive vertical fullscreen slider that moves its sections in opposite directions. We’ll be using jQuery, CSS Transitions and media queries to make the layout adaptive.

Vertical Showcase Slider with jQuery and CSS Transitions

In this tutorial we will create a very simplistic and responsive product slider for an online store or a portfolio. The idea is to have different sections in a fullscreen view: the image or preview, a navigation and the description. When navigating through the items, we will slide the preview section and the section with the description in opposite directions. The idea for this kind of “opposite” transition comes from the beautiful website of the National LGBT Museum which moves the left and right side in the same manner when navigating or scrolling the page.

Please note: this only works as intended in browsers that support the respective CSS properties.

The product images and information used in the demo are by IKEA.

So, let’s do this!

The Markup

We will have a main container which will wrap the following elements: a header, a wrapper for the content or descriptions and a wrapper for the slides:

<section id="ps-container" class="ps-container">

	<div class="ps-header">
		<h1>Vertical Showcase Slider</h1>
	</div><!-- /ps-header -->
	
	<div class="ps-contentwrapper">
	
		<div class="ps-content">
			<h2>Bernhard</h2>
			<span class="ps-price">£100</span>
			<p>With restful springiness in the seat; prevents static sitting and provides enhanced seating comfort. Padded seat and back for enhanced seating comfort. Soft, hardwearing and easy care leather, which ages gracefully.</p>
			<a href="http://www.ikea.com/gb/en/catalog/products/80163804/#/60203882">Buy this item</a>
		</div>
		
		<div class="ps-content">
			<!-- description item 2 -->
		</div>
		
		<div class="ps-content">
			<!-- description item 3 -->
		</div>
		
		<div class="ps-content">
			<!-- description item 4 -->
		</div>
		
		<div class="ps-content">
			<!-- description item 5 -->
		</div>
		
	</div><!-- /ps-contentwrapper -->
	
	<div class="ps-slidewrapper">
	
		<div class="ps-slides">
			<div style="background-image:url(images/1.jpg);"></div>
			<div style="background-image:url(images/2.jpg);"></div>
			<div style="background-image:url(images/3.jpg);"></div>
			<div style="background-image:url(images/4.jpg);"></div>
			<div style="background-image:url(images/5.jpg);"></div>
		</div>
		
		<nav>
			<a href="#" class="ps-prev"></a>
			<a href="#" class="ps-next"></a>
		</nav>
		
	</div><!-- /ps-slidewrapper -->
	
</section><!-- /ps-container -->

The wrapper for the slides will contain the same amount of divisions like the content wrapper and each division will have the respective image as background image. We will also have a navigation that will contain a previous and next anchor. These anchors will also have a background image, but we’ll set it dynamically.

Let’s add some style.

The CSS

Note that the CSS will not contain any vendor prefixes, but you will find them in the files.

Let’s first add a font that we’ve created with fontello.com. This font will only have one character and it’s the little shopping cart for the “Buy this item” link:

@font-face {
  font-family: 'icon';
  src: url("font/icon.eot");
  src: 
	url("font/icon.eot?#iefix") format('embedded-opentype'),
	url("font/icon.woff") format('woff'), 
	url("font/icon.ttf") format('truetype'), 
	url("font/icon.svg#icon") format('svg');
  font-weight: normal;
  font-style: normal;
}

Our aim is to create a layout that is 100% width and height of the screen, so our container will be positioned absolutely and we’ll set the overflow to hidden:

.ps-container {
	position: absolute;
	width: 100%;
	height: 100%;
	overflow: hidden;
	text-transform: uppercase;
	color: #555;
	background: #fff;
}

The width and height will be 100%. Note that we’ve set the height of the html to 100% as well (demo.css).

All the children of our main container will have a width of 50% and they’ll be of absolute positioning:

.ps-container > div {
	position: absolute;
	width: 50%;
}

There will be a couple of elements that will share the absolute positioning:

.ps-container > div > div,
.ps-slidewrapper > nav,
.ps-slides > div {
	position: absolute;
}

The header will have a height of 150 pixel and we’ll position it in the top left corner:

.ps-header {
	top: 0px;
	left: 0px;
	height: 150px;
	z-index: 1001;
	background: #fff;
}

The h1 will be styled as follows:

.ps-header h1 {
	color: #ccc;
	line-height: 150px;
	margin: 0;
	padding: 0 50px;
	font-weight: 200;
	font-size: 14px;
	letter-spacing: 10px;
}

The content wrapper will need the same top as the height of the header and we’ll set the overflow to hidden:

.ps-contentwrapper {
	top: 150px;
	bottom: 0px;
	overflow: hidden;
	z-index: 1000;
}

The inner divisions will occupy all the width and height of the parent and we’ll give it a bit of padding:

.ps-content {
	background: #fff;
	width: 100%;
	height: 100%;
	padding: 50px;
}

Let’s style the text elements. The headline and the paragraph will have some borders to suggest a chair:

.ps-content h2 {
	padding: 10px 15px;
	border-right: 1px solid #f2f2f2;
	border-bottom: 1px solid #f2f2f2;
	letter-spacing: 4px;
	margin: 10px 0 30px;
	text-align: right;
	font-weight: 700;
}

.ps-content p {
	line-height: 26px;
	font-size: 12px;
	letter-spacing: 2px;
	word-spacing: 10px;
	padding: 10px 15px;
	font-weight: 400;
	text-align: justify;
	border-left: 1px solid #f2f2f2;
	border-top: 1px solid #f2f2f2;
}

The price will be floating on the left side and we’ll give it the style of a box:

.ps-content span.ps-price {
	float: left;
	margin: 10px;
	width: 140px;
	height: 140px;
	line-height: 140px;
	text-align: center;
	color: #fff;
	background: #f7cfc6;
	background: rgba(247,197,185,0.8);
	font-size: 55px;
	font-weight: 200;
}

Note that we set a HEX background color before setting a RGBA one. Older browsers that don’t know what RGBA values are will ignore the second line and apply the first color.

The link will have a thick border and we’ll make it green on hover if we are not on a touch device (we use Modernizr for that):

.ps-content a:last-child {
	font-size: 14px;
	font-weight: 700;
	color: #555;
	letter-spacing: 4px;
	float: right;
	border: 3px solid #555;
	padding: 3px;
	text-indent: 4px;
}

.no-touch .ps-content a:last-child:hover {
	color: #b2d79d;
	border-color: #b2d79d;
}

We’ll add the little shopping cart icon by styling the pseudo-class :after:

.ps-content a:last-child:before {
	content: '53';
	font-family: 'icon';
	font-style: normal;
	font-weight: normal;
	speak: none;
	padding-right: 5px;
}

The container for the slides and navigation will be placed on the right side and it will have a height of 100%:

.ps-slidewrapper {
	right: 0px;
	top: 0px;
	height: 100%;
	overflow: hidden;
}

The wrapper for the slides will be stretched from top: 0px to bottom: 200px. This will keep its height elastic:

.ps-slides {
	top: 0px;
	bottom: 200px;
	width: 100%;
}

The inner divs, the ones that will contain the background image, will have a width and height of 100% and we’ll give them a inset box shadow that will serve as a subtle overlay over the main image preview. We don’t really know how big this division will get so we’ll give it an extremely big spread radius value:

.ps-slides > div {
	width: 100%;
	height: 100%;
	box-shadow: inset 0 0 0 9999px rgba(179,157,250,0.1);
}

The navigation will be positioned at the bottom of the slides container and we’ll give it a fixed height of 200 pixel:

.ps-slidewrapper > nav {
	width: 100%;
	height: 200px;
	bottom: 0px;
	right: 0px;
	z-index: 1000;
}

The previous and next link elements will be floating and we’ll also give them a inset box shadow to create a subtle overlay effect. They will also have a transition for non-touch devices:

.ps-slidewrapper > nav > a {
	width: 50%;
	height: 100%;
	position: relative;
	float: left;
	outline: none;
	box-shadow: inset 0 0 0 9999px rgba(207,227,206,0.8);
}

.ps-slidewrapper > nav > a:first-child {
	box-shadow: inset 0 0 0 9999px rgba(233,217,141,0.8);
}

.no-touch .ps-slidewrapper > nav > a {
	transition: box-shadow 0.4s ease-in-out;
}

.no-touch .ps-slidewrapper > nav > a:hover {
	box-shadow: inset 0 0 0 9999px rgba(246,224,121,0.1);
}

.no-touch .ps-slidewrapper > nav > a:first-child:hover {
	box-shadow: inset 0 0 0 9999px rgba(249,15,15,0.1);
}

The navigation anchors will have a pseudo-element that will be styled to appear as an arrow. For that we will add a left and top border and rotate them accordingly:

.ps-slidewrapper > nav > a:after {
	content: '';
	position: absolute;
	width: 100px;
	height: 100px;
	top: 50%;
	left: 50%;
	margin: -20px 0 0 -50px;
	transform: rotate(45deg);
	border-left: 1px solid #fff;
	border-top: 1px solid #fff;
}

.ps-slidewrapper > nav > a:first-child:after {
	transform: rotate(-135deg);
	margin: -80px 0 0 -50px;
}

The main previews and the navigation links are the elements that have a background image. We’ll set that image to stretch over the height of their container:

.ps-slides > div,
.ps-slidewrapper > nav > a {
	background-color: #fff;
	background-position: center top;
	background-repeat: no-repeat;
	background-size: auto 100%;
}

The next class is used dynamically when we want to slide an element in or out:

.ps-move {
	transition: top 400ms ease-out;
}

Last, but not least, we’ll define a media query for smaller devices. We only want the media query to change the style if we have JavaScript enabled since we have a completely different layout for the case JavaScript is disabled.

We need to set the children of the main container to 100% width:

@media screen and (max-width: 860px) {

	.js .ps-container > div {
		width: 100%;
	}

The header will be a bit smaller:

	.js .ps-header {
		height: 50px;
	}

	.js .ps-header h1 {
		line-height: 50px;
		padding: 0px 20px;
		letter-spacing: 4px;
	}

The wrapper for the preview slides will be positioned differently since we’ll place the content under it:

	.js .ps-slides {
		bottom: 320px;
		top: 50px;
	}

The navigation will be half its original height:

	.js .ps-slidewrapper > nav {
		height: 100px;
	}

The content wrapper will have a height of 220px and we’ll place it right above the navigation:

	.js .ps-contentwrapper {
		top: auto;
		height: 220px;
		bottom: 100px;
	}

Let’s change the sizes of the typographic elements:

	.js .ps-content {
		padding: 10px;
	}

	.js .ps-content h2 {
		border-right: none;
		font-size: 18px;
		margin: 10px 0;
		padding-top: 0;
	}

	.js .ps-content span.ps-price {
		font-size: 18px;
		width: 50px;
		height: 50px;
		line-height: 50px;
		font-weight: 700;
		margin-bottom: 0;
	}

We don’t have so much space, so let’s set a fixed height for the paragraph and make it scrollable:

	.js .ps-content p {
		line-height: 20px;
		border: none;
		padding: 5px 10px;
		height: 80px;
		overflow-y: scroll;
	}

The link will be a bit smaller and positioned to fit better into its context:

	.js .ps-content a:last-child {
		font-size: 13px;
		margin: 10px 20px 0 0;
	}
}

And that’s all the style! Now, let’s take a look at the JavaScript

The JavaScript

We will start by caching some elements and define some variables:

var $container = $( '#ps-container' ),
	$contentwrapper = $container.children( 'div.ps-contentwrapper' ),
	// the items (description elements for the slides/products)
	$items = $contentwrapper.children( 'div.ps-content' ),
	itemsCount = $items.length,
	$slidewrapper = $container.children( 'div.ps-slidewrapper' ),
	// the slides (product images)
	$slidescontainer = $slidewrapper.find( 'div.ps-slides' ),
	$slides = $slidescontainer.children( 'div' ),
	// navigation arrows
	$navprev = $slidewrapper.find( 'nav > a.ps-prev' ),
	$navnext = $slidewrapper.find( 'nav > a.ps-next' ),
	// current index for items and slides
	current = 0,
	// checks if the transition is in progress
	isAnimating = false,
	// support for CSS transitions
	support = Modernizr.csstransitions// transition end event
	// transition end event
	// https://github.com/twitter/bootstrap/issues/2870
	transEndEventNames = {
		'WebkitTransition' : 'webkitTransitionEnd',
		'MozTransition' : 'transitionend',
		'OTransition' : 'oTransitionEnd',
		'msTransition' : 'MSTransitionEnd',
		'transition' : 'transitionend'
	};

When the init function is called, the first thing to do is to show the first item and corresponding slide/image. Also, we need to update the navigation arrows background image with the right ones, meaning that we will use the same background images as defined for the previews. Finally, the initEvents function is called.

init = function() {

	// show first item
	var $currentItem = $items.eq( current ),
		$currentSlide = $slides.eq( current ),
		initCSS = {
			top : 0,
			zIndex : 999
		};

	$currentItem.css( initCSS );
	$currentSlide.css( initCSS );
	
	// update nav images
	updateNavImages();

	// initialize some events
	initEvents();

},
updateNavImages = function() {

	// updates the background image for the navigation arrows
	var configPrev = ( current > 0 ) ? $slides.eq( current - 1 ).css( 'background-image' ) : $slides.eq( itemsCount - 1 ).css( 'background-image' ),
		configNext = ( current < itemsCount - 1 ) ? $slides.eq( current + 1 ).css( 'background-image' ) : $slides.eq( 0 ).css( 'background-image' );

	$navprev.css( 'background-image', configPrev );
	$navnext.css( 'background-image', configNext );

},
adjustLayout = function() {

	$container.css( 'height', $window.height() );

},

We need to initialize the click event for both navigation elements and the transitionend event for both, items/descriptions and slides.

initEvents = function() {

	$navprev.on( 'click', function( event ) {

		if( !isAnimating ) {
			
			slide( 'prev' );
		
		}
		return false;

	} );

	$navnext.on( 'click', function( event ) {

		if( !isAnimating ) {
			
			slide( 'next' );
		
		}
		return false;

	} );

	// transition end event
	$items.on( transEndEventName, removeTransition );
	$slides.on( transEndEventName, removeTransition );
	
},

The main function is, of course, the slide function. The idea is to position the next item/slide to be shown above or below the current one (depending on which navigation element we click). Then we move the current item/slide out of the wrapper and slide in the new ones. We also keep the navigation elements’ background image updated.

slide = function( dir ) {

	isAnimating = true;

	var $currentItem = $items.eq( current ),
		$currentSlide = $slides.eq( current );

	// update current value
	if( dir === 'next' ) {

		( current < itemsCount - 1 ) ? ++current : current = 0;

	}
	else if( dir === 'prev' ) {

		( current > 0 ) ? --current : current = itemsCount - 1;

	}
		// new item that will be shown
	var $newItem = $items.eq( current ),
		// new slide that will be shown
		$newSlide = $slides.eq( current );

	// position the new item up or down the viewport depending on the direction
	$newItem.css( {
		top : ( dir === 'next' ) ? '-100%' : '100%',
		zIndex : 999
	} );
	
	$newSlide.css( {
		top : ( dir === 'next' ) ? '100%' : '-100%',
		zIndex : 999
	} );

	setTimeout( function() {

		// move the current item and slide to the top or bottom depending on the direction 
		$currentItem.addClass( 'ps-move' ).css( {
			top : ( dir === 'next' ) ? '100%' : '-100%',
			zIndex : 1
		} );

		$currentSlide.addClass( 'ps-move' ).css( {
			top : ( dir === 'next' ) ? '-100%' : '100%',
			zIndex : 1
		} );

		// move the new ones to the main viewport
		$newItem.addClass( 'ps-move' ).css( 'top', 0 );
		$newSlide.addClass( 'ps-move' ).css( 'top', 0 );

		// if no CSS transitions set the isAnimating flag to false
		if( !support ) {

			isAnimating = false;

		}

	}, 0 );

	// update nav images
	updateNavImages();

};

And that’s it! I hope you enjoyed this tutorial and find it inspiring!

Tagged with:

Manoela Ilic

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

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

Feedback 25

Comments are closed.
  1. Mary Lou, you are a goddess. You have impeccable timing, as I’m just about to develop a slider similar to yours with the previous and next preview thumbs and and active slide with additional information showing. Genius!

  2. Simple and excellent. I can imagine extending it adding a scrollable div (similar to a slideshow with controls) to add more products. Definitely a solid base 🙂

  3. IMHO, the layout for mobile devices needs work.

    Resizing the window significantly shrinks the images but the font size remains the same.

    Ideally, you’d want the main image to take up, say, 50% of the available screen space, give 30% to the text (preferably with a reduced font size or, if that renders it too small to read, a reduction in the number of words) and 20% to the next two images.

  4. greattttttttttttttttttttttt work. really i love your mind. such a highly creative one . i am huge fan of yours . i always follow your work and using it in my work. i wish that i could see you some day.

  5. This is very cool! I do have a concern about semantics. Wouldn’t it be better for the descriptions (.ps-content) to be with the slides in the markup? I want to implement this in my own site, but I don’t want to take the image captions/descriptions out of their context.

  6. Hi guys I need to use multi slider in same page but next & prev not working any I idea how to fix it

    Thankx

  7. The layout looks great!
    Would it be easy to include the horizontal carousel within the big image? For instance, this carousel would show photos of different angles of the given chair rather than showing a single image.

  8. Hi

    I am trying to use this tutorial as a wordpress plugin, everything is fine but a single js error i get from the console has ruined the whole thing and animation does not start, i can’t see the images and the footer of my page is gone,

    here is the js error i get from the console:

    ReferenceError: assignment to undeclared variable transEndEventName [Break On This Error] transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ],

    it’s on line 35 of slider.js

    can anyone help please?

    • There was a ; after the declaration of “transEndEventNames”. Maybe that was the problem? Thanks for the feedback! Cheers, ML

  9. I replaced the ; with a , and the error is gone now, but now I am having another error in the script I inserted into my page, here is the error from my console:
    ReferenceError: Slider is not defined [Break On This Error] Slider.init();

    please help

  10. Never mind, solved it by my self. It was because wordpress was not adding the scripts in the sequence needed to get this plugin working, had to hand code some the scripts and now it’s working.

    thanks for the great tut ML, Cheers

  11. Any Ideas how we could add mouse roller support to the slides? I am looking for a way that when the mouse is hovering over the slides or nav section the mouse roller would change the slides too.

    Plus, I tried it on opera mobile emulator and it does not look good there although it’s very well responsive on browser resize but can’t see the slides and half of the texts also jumps out of the screen.

  12. Hi, i have a problem with modernize. There is no interactivity with buttons to change pictures, and my web browser says he can’t find modernizr variable in ‘support = Modernizr.csstransitions,’.

    Why ?

    Thank you.

  13. how can we automate that slider..
    like image slider with automatic images changes…
    please be descriptive…

    thank you very much!!!!!!!!!!!!!!!!!!

  14. Hi,
    Thanks in advance for the resource, Mary.
    I have a question, i’m trying to nest the section tag (which includes all the html content) into a div wrapper, however the automatic height of the wrapper div it seems does not work, then, i need to nest other contents below; but the other contents just stay behind the slider and not underneath.
    Can anyone help me with this trouble?
    Thanks.