- Overview
- Documents
VERTICAL SHOWCASE SLIDER WITH JQUERY AND CSS TRANSITIONS
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 useModernizr 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, theinitEvents 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!