From our sponsor: Meco is a distraction-free space for reading and discovering newsletters, separate from the inbox.
The FWA landing page has a really nice content slider that plays with 3D perspective on screenshots and animates them in an interesting way. Today we’d like to recreate part of that effect and make a simple content slideshow with some fancy 3D animations. The slideshow won’t be the same as the one on the FWA page: the items won’t be “floating” or moving on hover and we’ll only have a simple navigation.
If you have seen the effect over at the FWA landing page you will notice that the movement directions of the screenshots are random (moving up or down and sliding to the sides). We want to achieve the same effect by randomly adding some data-attributes that control the type of animation.
Please note that we’ll be using CSS 3D Transforms and CSS Animations which might not work in older or mobile browsers.
For the demo we are using some website screenshots from Zurb’s Responsive Gallery.
So, let’s get started!
The Markup
The slideshow has a main container with the class and ID “slideshow” and we can use an ordered list for our slides. Each list item contains a description with a title and a paragraph. It will also contain a division with the class “tiltview” where we will add our screenshots. The classes “col” an “row” will help us set the right layout for the inner anchors:
<div class="slideshow" id="slideshow"> <ol class="slides"> <li class="current"> <div class="description"> <h2>Some Title</h2> <p>Some description</p> </div> <div class="tiltview col"> <a href="http://grovemade.com/"><img src="img/1_screen.jpg"/></a> <a href="https://tsovet.com/"><img src="img/2_screen.jpg"/></a> </div> </li> <li> <div class="description"> <!-- ... --> </div> <div class="tiltview row"> <!-- ... --> </div> </li> <li> <!-- ... --> </li> </ol> </div>
We’ll also add a navigation element in our JavaScript which we’ll place right after the ordered list. It will consist of a nav
with the right amount of spans.
Let’s already have a thought on how we will control the animations for each screenshot. In our script we set a data-attribute for a random incoming and outgoing animation. We’ll use the data-attributes data-effect-in
and data-effect-out
to control our animations in the CSS. We’ll check out the values for those attributes in a while. Let’s first check out the main style of the slideshow.
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 (-webkit-).
Our slideshow wrapper and the ordered list will have the following style:
.slideshow { position: relative; margin-bottom: 100px; } .slides { list-style: none; padding: 0; margin: 0; position: relative; height: 500px; width: 100%; overflow: hidden; background: #ddd; color: #333; }
The slideshow will be 500px high and we need to set the overflow to hidden, so that we don’t see the items fly out.
When we can’t build our slideshow because JavaScript is not enabled, we need to make sure that all slides are shown, so we set the height to auto
:
.no-js .slides { height: auto; }
Each list item will be positioned absolutely and occupy all available width and height. By default, we’ll set the visibility to hidden
.
Each slide will also serve as the perspective container and we’ll define a perspective value of 1600px:
.slides > li { width: 100%; height: 100%; position: absolute; visibility: hidden; perspective: 1600px; }
Let’s not forget the fallback:
.no-js .slides > li { position: relative; visibility: visible; }
The navigation which is added dynamically, will appear as a set of lines. Each navigation item is a span and although we are using a tiny line, we want to make sure that the clickable area is actually bigger. This we can simulate by adding a white border:
.slideshow > nav { text-align: center; margin-top: 20px; } .slideshow > nav span { display: inline-block; width: 60px; height: 25px; border-top: 10px solid #fff; border-bottom: 10px solid #fff; background-color: #ddd; cursor: pointer; margin: 0 3px; transition: background-color 0.2s; } .slideshow > nav span:hover { background-color: #333; } .slideshow > nav span.current { background-color: #aaa; }
The description will fill half of the width and since we want a transition on the opacity, we need to set it to 0 initially:
.description { width: 50%; padding: 2em 4em; font-size: 1.5em; position: relative; z-index: 1000; opacity: 0; } .no-js .description { opacity: 1; } .description h2 { font-size: 200%; }
Now, let’s style the most crucial element in our slideshow. The division with the class “tiltview” will help us put our items into perspective. We need to add preserve-3d
as transform style because some inner items will need to move on the Z-axis in some animations.
The “tiltview” wrapper will be centered by setting the top to 50% and transforming it -50% on the Y-axis. We’ll also rotate it on the X and Z-axis to create the 3D look:
.tiltview { position: absolute; left: 50%; width: 50%; top: 50%; transform-style: preserve-3d; transform: translateY(-50%) rotateX(60deg) rotateZ(35deg); }
And the anchors and images will have the following style (the outline helps to avoid jagged edges in Firefox):
.tiltview a { outline: 1px solid transparent; } .tiltview a, .tiltview a img { max-width: 100%; display: block; margin: 0 auto; } .tiltview a:first-child { margin-bottom: 30px; }
For the row and column cases we’ll set the widths accordingly:
.tiltview.row a { width: 48%; width: calc(50% - 15px); margin: 0; } .tiltview.row a:nth-child(2) { left: 50%; left: calc(50% + 15px); position: absolute; top: 0; }
In our script we will use the classes “show” and “hide” to control the visibility of the slides:
/* Show/Hide */ .slides > li.current, .slides > li.show { visibility: visible; }
The description will fade in and out:
.description { transition: opacity 0.75s; } .current .description, .show .description { opacity: 1; } .hide .description { opacity: 0; }
As we mentioned before, we’ll control the animations by using some data-attributes. We have to define two types of animations: the incoming one (when we show the next slide) and the outgoing one (when we hide the previous slide).
We want to be able to animate the items in all possible directions, so we’ll need six different types: move up, move down, slide up, slide down, slide left, slide right. This makes a total of 12 animations (incoming and outgoing).
So, let’s define the first one for moving the outgoing element up:
/***********************/ /* Move up */ /***********************/ .hide[data-effect-out="moveUpOut"] .tiltview a { animation: moveUpOut 1.5s both; } .hide[data-effect-out="moveUpOut"] .tiltview a:nth-child(2) { animation-delay: 0.25s; } @keyframes moveUpOut { 25% { animation-timing-function: cubic-bezier(1.000, 0.000, 0.000, 1.000); transform: translateZ(-30px); } 100% { transform: translateZ(3000px); } }
We define a slight animation delay for the second item and we use a custom cubic-bezier timing function to add some interesting momentum.
The second animation for this movement is the incoming one which has the initial and end step reversed:
.show[data-effect-in="moveUpIn"] .tiltview a { animation: moveUpIn 1.5s 0.5s both; } .show[data-effect-in="moveUpIn"] .tiltview a:nth-child(2) { animation-delay: 0.75s; } @keyframes moveUpIn { 0% { animation-timing-function: cubic-bezier(1.000, 0.000, 0.000, 1.000); transform: translateZ(-3000px); } 75% { transform: translateZ(30px); } 100% { transform: translateZ(0); } }
As you might have noticed, we could simplify the animation delay for the “hide” and the “show” case, but keeping the two rules separated will allow for easier adaption in case you’d like to define some different delays.
The resting animations are as follows:
/***********************/ /* Move down */ /***********************/ .hide[data-effect-out="moveDownOut"] .tiltview a { animation: moveDownOut 1.5s both; } .hide[data-effect-out="moveDownOut"] .tiltview a:nth-child(2) { animation-delay: 0.25s; } @keyframes moveDownOut { 25% { animation-timing-function: cubic-bezier(1.000, 0.000, 0.000, 1.000); transform: translateZ(30px); } 100% { transform: translateZ(-3000px); } } .show[data-effect-in="moveDownIn"] .tiltview a { animation: moveDownIn 1.5s 0.5s both; } .show[data-effect-in="moveDownIn"] .tiltview a:nth-child(2) { animation-delay: 0.75s; } @keyframes moveDownIn { 0% { animation-timing-function: cubic-bezier(1.000, 0.000, 0.000, 1.000); transform: translateZ(3000px); } 75% { transform: translateZ(-30px); } 100% { transform: translateZ(0); } } /***********************/ /* Slide up */ /***********************/ .hide[data-effect-out="slideUpOut"] .tiltview a { animation: slideUpOut 1.5s both; } .hide[data-effect-out="slideUpOut"] .tiltview a:nth-child(2) { animation-delay: 0.25s; } @keyframes slideUpOut { 25% { animation-timing-function: cubic-bezier(1.000, 0.000, 0.000, 1.000); transform: translateY(30px); } 100% { transform: translateY(-3000px); } } .show[data-effect-in="slideUpIn"] .tiltview a { animation: slideUpIn 1.5s 0.5s both; } .show[data-effect-in="slideUpIn"] .tiltview a:nth-child(2) { animation-delay: 0.75s; } @keyframes slideUpIn { 0% { animation-timing-function: cubic-bezier(1.000, 0.000, 0.000, 1.000); transform: translateY(3000px); } 75% { transform: translateY(-30px); } 100% { transform: translateY(0); } } /***********************/ /* Slide down */ /***********************/ .hide[data-effect-out="slideDownOut"] .tiltview a { animation: slideDownOut 1.5s both; } .hide[data-effect-out="slideDownOut"] .tiltview.row a:nth-child(2), .hide[data-effect-out="slideDownOut"] .tiltview.col a:first-child { animation-delay: 0.25s; } @keyframes slideDownOut { 25% { animation-timing-function: cubic-bezier(1.000, 0.000, 0.000, 1.000); transform: translateY(-30px); } 100% { transform: translateY(3000px); } } .show[data-effect-in="slideDownIn"] .tiltview a { animation: slideDownIn 1.5s 0.5s both; } .show[data-effect-in="slideDownIn"] .tiltview.row a:nth-child(2), .show[data-effect-in="slideDownIn"] .tiltview.col a:first-child { animation-delay: 0.75s; } @keyframes slideDownIn { 0% { animation-timing-function: cubic-bezier(1.000, 0.000, 0.000, 1.000); transform: translateY(-3000px); } 75% { transform: translateY(30px); } 100% { transform: translateY(0); } } /***********************/ /* Slide left */ /***********************/ .hide[data-effect-out="slideLeftOut"] .tiltview a { animation: slideLeftOut 1.5s both; } .hide[data-effect-out="slideLeftOut"] .tiltview a:nth-child(2) { animation-delay: 0.25s; } @keyframes slideLeftOut { 25% { animation-timing-function: cubic-bezier(1.000, 0.000, 0.000, 1.000); transform: translateX(30px); } 100% { transform: translateX(-5000px); } } .show[data-effect-in="slideLeftIn"] .tiltview a { animation: slideLeftIn 1.5s 0.5s both; } .show[data-effect-in="slideLeftIn"] .tiltview a:nth-child(2) { animation-delay: 0.75s; } @keyframes slideLeftIn { 0% { animation-timing-function: cubic-bezier(1.000, 0.000, 0.000, 1.000); transform: translateX(3000px); } 75% { transform: translateX(-30px); } 100% { transform: translateX(0); } } /***********************/ /* Slide right */ /***********************/ .hide[data-effect-out="slideRightOut"] .tiltview a { animation: slideRightOut 1.5s both; } .hide[data-effect-out="slideRightOut"] .tiltview.col a:nth-child(2), .hide[data-effect-out="slideRightOut"] .tiltview.row a:first-child { animation-delay: 0.25s; } @keyframes slideRightOut { 25% { animation-timing-function: cubic-bezier(1.000, 0.000, 0.000, 1.000); transform: translateX(-30px); } 100% { transform: translateX(3000px); } } .show[data-effect-in="slideRightIn"] .tiltview a { animation: slideRightIn 1.5s 0.5s both; } .show[data-effect-in="slideRightIn"] .tiltview.col a:nth-child(2), .show[data-effect-in="slideRightIn"] .tiltview.row a:first-child { animation-delay: 0.75s; } @keyframes slideRightIn { 0% { animation-timing-function: cubic-bezier(1.000, 0.000, 0.000, 1.000); transform: translateX(-5000px); } 75% { transform: translateX(30px); } 100% { transform: translateX(0); } }
Note that for some animations we need to define the animation delay for the first child instead of the second. We need to do this so that the anchors don’t overlap each other for some directions.
When we don’t have support for CSS 3D Transforms or transform-style: preserve-3d
, then we want to provide a simple fallback:
/* Fallback for no 3D Transforms and no preserve-3d */ .no-csstransformspreserve3d .show .tiltview a, .no-csstransformspreserve3d .hide .tiltview a, .no-csstransforms3d .show .tiltview a, .no-csstransforms3d .hide .tiltview a { animation: none !important; } .no-csstransforms3d .tiltview.col { top: -50%; } .no-csstransforms3d .tiltview.row { top: 20px; }
And last, but not least, we need to make sure that we have a reasonable look for smaller screens. In this case we want the anchors with their screenshots to be just decoration, so we’ll set their opacity lower and make them be not clickable:
@media screen and (max-width: 65.3125em) { .description, .tiltview { width: 100%; } .tiltview { left: 0; opacity: 0.3; pointer-events: none; } } @media screen and (max-width: 33.75em) { .description { font-size: 1.1em; } .slideshow > nav span { width: 20px; height: 40px; margin: 0 10px; } } @media screen and (max-width: 24em) { .slides { height: 320px; } .description { font-size: 1em; padding: 1.4em; } .no-csstransforms3d .tiltview.col, .no-csstransforms3d .tiltview.row { top: 0; } }
And that’s the style! Let’s do our slideshow script.
The JavaScript
We will start by initializing some variables, like the two arrays with the animation class names that control the incoming and outgoing of our items. These will be picked randomly and set to the items when we navigate the slideshow. Other variables are the items, the current value of the selected item (we assume this will be the first one) and the total number of items inside the slider:
function TiltSlider( el, options ) { this.el = el; // available effects for the animations (animation class names) - when an item comes in or goes out this.animEffectsOut = ['moveUpOut','moveDownOut','slideUpOut','slideDownOut','slideLeftOut','slideRightOut']; this.animEffectsIn = ['moveUpIn','moveDownIn','slideUpIn','slideDownIn','slideLeftIn','slideRightIn']; // the items this.items = this.el.querySelector( 'ol.slides' ).children; // total number of items this.itemsCount = this.items.length; if( !this.itemsCount ) return; // index of the current item this.current = 0; this.options = extend( {}, this.options ); extend( this.options, options ); this._init(); }
In order to navigate the slideshow we will add some navigation spans that, when clicked, will make the respective slideshow item appear. The total number of spans will be the same as the total number of items. Let’s add the navigation to our component:
TiltSlider.prototype._addNavigation = function() { // add nav "dots" this.nav = document.createElement( 'nav' ) var inner = ''; for( var i = 0; i < this.itemsCount; ++i ) { inner += i === 0 ? '' : ''; } this.nav.innerHTML = inner; this.el.appendChild( this.nav ); this.navDots = [].slice.call( this.nav.children ); }
Next, we need to bind the onclick event to the navigation spans. If we click on any other but the current span, then the current item should animate out and the new one should animate in.
TiltSlider.prototype._initEvents = function() { var self = this; // show a new item when clicking the navigation "dots" this.navDots.forEach( function( dot, idx ) { dot.addEventListener( 'click', function() { if( idx !== self.current ) { self._showItem( idx ); } } ); } ); }
We need to reference and work with the current item and also the next one that has to appear. We will add and remove classes from both of them in order to apply the respective animations. The animation (i.e. the data-attribute value) itself will be randomly picked from our animEffectsOut
and animEffectsIn
arrays as described before.
TiltSlider.prototype._showItem = function( pos ) { if( this.isAnimating ) { return false; } this.isAnimating = true; classie.removeClass( this.navDots[ this.current ], 'current' ); var self = this, // the current item currentItem = this.items[ this.current ]; this.current = pos; // next item to come in var nextItem = this.items[ this.current ], // set random effects for the items outEffect = this.animEffectsOut[ Math.floor( Math.random() * this.animEffectsOut.length ) ], inEffect = this.animEffectsIn[ Math.floor( Math.random() * this.animEffectsOut.length ) ]; currentItem.setAttribute( 'data-effect-out', outEffect ); nextItem.setAttribute( 'data-effect-in', inEffect ); classie.addClass( this.navDots[ this.current ], 'current' ); var cntAnims = 0, // the number of elements that actually animate inside the current item animElemsCurrentCount = currentItem.querySelector( '.tiltview' ).children.length, // the number of elements that actually animate inside the next item animElemsNextCount = nextItem.querySelector( '.tiltview' ).children.length, // keep track of the number of animations that are terminated animEndCurrentCnt = 0, animEndNextCnt = 0, // check function for the end of each animation isFinished = function() { ++cntAnims; if( cntAnims === 2 ) { self.isAnimating = false; } }, // function for the end of the current item animation onEndAnimationCurrentItem = function() { ++animEndCurrentCnt; var endFn = function() { classie.removeClass( currentItem, 'hide' ); classie.removeClass( currentItem, 'current' ); isFinished(); }; if( !isSupported ) { endFn(); } else if( animEndCurrentCnt === animElemsCurrentCount ) { currentItem.removeEventListener( animEndEventName, onEndAnimationCurrentItem ); endFn(); } }, // function for the end of the next item animation onEndAnimationNextItem = function() { ++animEndNextCnt; var endFn = function() { classie.removeClass( nextItem, 'show' ); classie.addClass( nextItem, 'current' ); isFinished(); }; if( !isSupported ) { endFn(); } else if( animEndNextCnt === animElemsNextCount ) { nextItem.removeEventListener( animEndEventName, onEndAnimationNextItem ); endFn(); } }; if( isSupported ) { currentItem.addEventListener( animEndEventName, onEndAnimationCurrentItem ); nextItem.addEventListener( animEndEventName, onEndAnimationNextItem ); } else { onEndAnimationCurrentItem(); onEndAnimationNextItem(); } classie.addClass( currentItem, 'hide' ); classie.addClass( nextItem, 'show' ); }
And that’s it! We hope you enjoyed this tutorial and find it useful!
Note that IE11 still does not support transform-style: preserve-3d
, so the fallback will be shown there.
hi,
please auto start code?
thks
how we can add next and previous button in this slider
How do I Coloka autoplay, I followed the comments and suggestions, but not worked, then Raphael Araujo P, u could pass this option by email
Anyone else who has problems with the latest Firefox (crashes all the time).
I want to insert 7 images in the slide. the current one is having 6.
Which line do i have to alter under which .js script.
any one’s help will be much appreciated.
Hi, just to know how to add “PAUSE” function on roll over?
Hi there! Just to know if there is any way to pause on rollover or maybe hoy to add a pause button…. any idea???
By the way… amazing work! I love it!
Raphael Araújo P. thank you very much.
I am getting error while loading
Don’t forget to add Jquery file. Then it will work properly.
Help would be very much appreciated. I can only get the slider to move from the first slide to the second and then it stops. Am I missing some instruction somewhere ?
how may slides do you have?
hai its very nice i like it
thanks
Mary Lou
Slider looks good but I can’t get it to progress past the second slide! Any ideas on how to fix?
Got the same problem. Can’t get further than the second slider 🙁
It’s e very, very nice slider. Unfotunately I am not able to force it to move.
I read over all the comments and tried all solituions I fund hier. My slider do not want to cooperate. Sory.
Awesome work! I just like it.
It stopped by the second slider, but just because I overwrite one class in another css file. (by me it was .hide)
Maybe the others will find the solution too 😉
I m new to javascript so i couldn’t understand codes like this one -name- : function. Can u suggest me some books or library that can help me to understand Javascript code properly?? Thanks in advance!
hello Mary Lou
it’s very nice and clean slider
i just want to ask can i use animate.css with this slider
Just a note to anyone who’s embedded this into a Drupal or WordPress site or similar, your markup doesn’t have to be identical (eg. drupal spits out much more html than needed) as long as you have alternating row/col on the li’s and the images are wrapped in anchor tags you should be fine, I think a lot of the issues people have had with this results from images not been in anchor tags.
Hope that helps peeps!
I’ve got it working mostly great in WordPress, except the animations aren’t working and I’m not sure why. They’re loading just fine, just not doing the flying in and out thing 🙁 Any ideas?
Thanks Mary Lou,you are big programmer with nice idea
How can I make auto play?
I want to set up random slideshow view. How do I do it?
got it working AT LAST
step 1) get the jquery plugin at the head section of your site,
step 2) follow by the code given,
$(document).ready(function(){
var el=1;
repeat(7000,function(){
if(el==7){
$(‘#slideshow nav span:nth-child(1)’).click();
el=2;
}
else{
$(‘#slideshow nav span:nth-child(‘+el+’)’).click();
el=el+1;
}
});
});
make sure the value el==7 corresponds with the no of slides in your page or
step 3) add marys classie.js tiltslider.js at end of your code but in body tag.
step 4) add wait.js and viola email me at michael00783@gmail.com if you need any help
Thanks Michael. This is a little how I use to make it work for those that want make it auto play.
$(document).ready(function(){
var el=1;
var elements = $(“#slideshow ol li”);
var dots = $(“#slideshow nav span”);
setInterval(function(){
if(el==elements.length){
$(dots[1]).click();
el=2;
}
else{
$(dots[el]).click();
el=el+1;
}
}, 5000);
});
Hey Michael,
Could you explain what you mean by `the jQuery Plugin`?
Then the code part where should we add this. Just before the body in a separate
Thanks
Help me please . How can I make auto play?
Hiii try this. Ofcourse you need jquery to be loaded first.
$(document).ready(function(){
var el=1;
var elements = $(“#slideshow ol li”);
var dots = $(“#slideshow nav span”);
setInterval(function(){
if(el==elements.length){
$(dots[1]).click();
el=2;
}
else{
$(dots[el]).click();
el=el+1;
}
}, 5000);
});
did you success to auto play?
I have seen several comments about autoplay, but cannot get it to work. Can someone please tell me in simpleton terms where to place the relevant pieces of code to make it autoplay?
Thanks 🙂
*jquery is put in the head tag,
*the tweak code (el=7…) is the head tag ,
*wait.js tiltslider.js classie.js is put jst b4 the tag.
Fabulous and exactly what I’m looking for for my new site.
I’ve got it set up as a nice WordPress loop, but the nav buttons don’t appear automatically (I added them into the loop manually, but I suspect something’s missing). Everything for the current slide appears correctly and the right number of nav paginators are there, but the slides won’t advance. I have jQuery called in the header and classie & tiltSlider js files in the footer. No animation, no advancing to the next slide. I’m not even looking for an autoplay feature. I just want to get this working properly on my WordPress site. Help??
Well, I do have the loop working now, and the nav items showing automatically. I even have autoplay working. What is *not* working are the animations for the images. Not sure what I need to do differently to make sure they work. I’ve checked FF, Safari and Chrome – mine doesn’t work, but the demo does in all 3. Argh! Anyone? This is on a custom WordPress dev site right now. It won’t be live until the site’s done.
please i want this slideshow autoplay ????
how to make this slider autoplay
please help us
What a excellent looking slideshow. It is so good I want to make it into a Joomla Module. So started with it got everything in the right place (I think) but encounter the same problem as so many others. It won’t go past the 2nd slide.
Now there are numerous solutions provided by others but somehow they are all pretty lazy when it comes with explaining.
Things like jQuery is put in the head tag would be nice to tell what you exactly mean(Micheal). Do we need to include jQuery or is it meant to add something else everyone is missing?
I added the no-js to the tag in Joomla but this messes up the entire slideshow and makes it display all slides below each other.
Once again it is a brilliant looking slideshow like everything else from Mary Lou but would love to solve this problem not only for me but for all the other users out there.