From our sponsor: Ready to show your plugin skills? Enter the Penpot Plugins Contest (Nov 15-Dec 15) to win cash prizes!
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.
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.
Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.
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!
The museum’s site was confusing me… I like your version! Thank you.
Tnkx for sharing….. i like this work !
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!
nice…..
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 🙂
Very nice, comes in handy…
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.
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.
Good work , Thanks for your share .
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.
Hi guys I need to use multi slider in same page but next & prev not working any I idea how to fix it
Thankx
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.
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
tried it, but the error is still there
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
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
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.
Does not work with IE8. Can you help me?
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.
¡Es hermoso!
how can we automate that slider..
like image slider with automatic images changes…
please be descriptive…
thank you very much!!!!!!!!!!!!!!!!!!
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.
So cool! Thanks for the code, beautiful design.
I have put this into a WordPress template – not sure if anyone would want it?
I also added in a slideshow for each ‘product’. You can see it here – http://www.jacoby.co.za/jacoby