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 are going to create an image gallery with a Polaroid look. We will have albums that will expand to sets of slightly rotated thumbnails that pop out on hover. The full image will slide in from the bottom once a thumbnail is clicked. In the full image view the user can navigate through the pictures or simply choose another thumbnail to be displayed.
For this gallery we will be using the 2D Transform plugin to animate rotations.
The beautiful photos used in the demo are by talented Tetsumo. Visit his blog here.
Let’s get started!
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 Markup
We are going to include all our HTML in a div with the class “pp_gallery”. It will consist of the loading div, the navigation that will appear when the full image is viewed and the main thumbnails container.
Inside of the thumbnails container which will have the class “pp_thumbContainer”, we will have several divs for the albums and a div for going back to the album view. Each album will contain the thumbnails with a description wrapped in a div with the class “content”. We will also include a div element for the description of the album itself.
<div id="pp_gallery" class="pp_gallery"> <div id="pp_loading" class="pp_loading"></div> <div id="pp_next" class="pp_next"></div> <div id="pp_prev" class="pp_prev"></div> <div id="pp_thumbContainer"> <div class="album"> <div class="content"> <img src="images/album1/thumbs/1.jpg" alt="images/album1/1.jpg" /> <span>The Sixties by Tetsumo</span> </div> <div class="content"> <img src="images/album1/thumbs/2.jpg" alt="images/album1/2.jpg" /> <span>The Sixties by Tetsumo</span> </div> ... <div class="descr"> The Sixties </div> </div> <div class="album" style="bottom:-90px;"> ... </div> ... <div class="pp_back">Albums</div> </div> </div>
The HTML structure of the dynamically created full image preview will be the following:
<div id="pp_preview" class="pp_preview"> <img src="images/album1/1.jpg" /> <div class="pp_descr"><span>Description</span></div> </div>
Now, let’s take a look at the style.
The CSS
We will start with a reset and some general styles for the body:
*{ margin:0; padding:0; } body{ background:#000 url(../bg.jpg) repeat center center; font-family:"Myriad Pro", "Trebuchet MS", Helvetica, sans-serif; font-size:12px; color: #fff; overflow:hidden; }
A structured background fits very nicely, so we choose to add a wood texture. You can find this and more textures on Webtreats.
Next, we will style the loading div and the navigation for stepping through the pictures when we are in the preview mode:
.pp_loading{ display:none; position:fixed; top:50%; left:50%; margin:-35px 0px 0px -35px; background:#fff url(../icons/loader.gif) no-repeat center center; width:70px; height:70px; z-index:999; opacity:0.7; -moz-border-radius:10px; -webkit-border-radius:10px; border-radius:10px; } .pp_next, .pp_prev{ cursor:pointer; top:50%; margin-top:-16px; width:32px; height:32px; position:fixed; text-align:center; border:1px solid #111; color:#fff; -moz-box-shadow:0px 0px 3px #000; -webkit-box-shadow:0px 0px 3px #000; box-shadow:0px 0px 3px #000; } .pp_next{ right:-40px; background:#222 url(../icons/next.png) no-repeat center center; } .pp_prev{ left:-40px; background:#222 url(../icons/prev.png) no-repeat center center; }
The loading div is set to the center of the page by using the 50% negative margin trick. When the position is fixed, we can set the top and left to 50% and add a top and left margin to the negative value of half of the element’s width, or height, respectively.
The same we do for the navigation items, just that here will only center the elements vertically.
The thumbnails container will be positioned at the bottom of the page:
#pp_thumbContainer{ position:fixed; bottom:0px; left:0px; height:65px; width:100%; }
The albums are hidden initially. When we load the page, they will slide in from the bottom, so we will set the initial bottom value to -90 pixels:
#pp_thumbContainer .album{ position:absolute; width:200px; height:65px; bottom:-90px; }
The left value for the positioning of the albums will be calculated dynamically on page load. We will spread all the albums evenly throughout the width of the page.
The description of the album and the back element will share some styles:
.album .descr, .pp_back{ position:absolute; bottom:0px; left:-16px; background:#222; text-align:center; border:1px solid #111; padding:5px; cursor:pointer; width:169px; color:#fff; cursor:pointer; text-shadow:0px 0px 1px #fff; -moz-box-shadow:1px 1px 4px #000; -webkit-box-shadow:1px 1px 4px #000; box-shadow:1px 1px 4px #000; }
…but not all. We will overwrite and add the values that are specific to the .pp_back class:
.pp_back{ text-transform:uppercase; bottom:120px; left:-100px; width:80px; }
The wrapper for the image and the image title will have the following style:
#pp_thumbContainer .content{ position:absolute; top:0px; height:155px; cursor:pointer; }
These wrappers will also be spread into position dynamically in our JavaScript. We set the top value to 0, so that all the thumbnails align to the top of the thumbnails container. We don’t want vertical thumbnails to stick out.
The thumbnail will have a white border and a box shadow:
#pp_thumbContainer img{ border:5px solid #fff; -moz-box-shadow:1px 1px 7px #000; -webkit-box-shadow:1px 1px 7px #000; box-shadow:1px 1px 7px #000; }
The description for each image is invisible. We will only use it to fill the description element for the full view.
#pp_thumbContainer .content span{ display:none; }
The wrapper for the full image will be positioned outside of the page by setting the top value to 150%. Once the image is loaded, we will slide it in from the bottom. We are setting the left value to 50% since we want to center the picture. Since we don’t know the width and height of the picture yet, we cannot set any negative margins yet. We will do that in our JavaScript function.
.pp_preview{ position:fixed; top:150%; left:50%; }
The full image will have a bigger white border at its bottom where we will insert the description:
.pp_preview img{ position:absolute; top:0px; left:0px; border:10px solid #fff; border-bottom:45px solid #fff; -moz-box-shadow:1px 1px 7px #000; -webkit-box-shadow:1px 1px 7px #000; box-shadow:1px 1px 7px #000; }
The description will be at the bottom of the preview element. In the JavaScript we will set the width and height of the preview div to the one of the image dynamically, so the description will be positioned at the thicker bottom border of the image.
.pp_descr{ height:45px; line-height:45px; font-size:20px; width:100%; bottom:0px; left:0px; position:absolute; text-align:center; color:#00021c; }
We will cufonize the description using a nice hand written font (see at the end).
And now, let’s add some real magic!
The JavaScript
There will be many animations throughout the JavaScript. The albums will slide in from the bottom when the page loads. Once an album is clicked, we will spread the thumbnails evenly by giving them an according left value. When we choose a thumbnail, we need to create the preview div and slide it in from the bottom. The image will get resized so that it fits into the window.
Let’s start by defining some initial variables. First, we want to know if we are dealing with Internet Explorer since we don’t want to use the rotation of the full image here (it’s buggy when used together with the slide in animation):
var ie = false; if ($.browser.msie) { ie = true; }
We will use some index variables for the navigation and save some elements:
//current album/image displayed var current = -1; var album = -1; //windows width var w_width = $(window).width(); //caching var $albums = $('#pp_thumbContainer div.album'); var $loader = $('#pp_loading'); var $next = $('#pp_next'); var $prev = $('#pp_prev'); var $images = $('#pp_thumbContainer div.content img'); var $back = $('#pp_back');
We want to spread the albums evenly throughout the page, so we will calculate the according left value:
var nmb_albums = $albums.length; var spaces = w_width/(nmb_albums+1); var cnt = 0; //preload all the images (thumbs) var nmb_images = $images.length; var loaded = 0; $images.each(function(i){ var $image = $(this); $('<img />').load(function(){ ++loaded; if(loaded == nmb_images){ //let's spread the albums evenly at the bottom of the page $albums.each(function(){ var $this = $(this); ++cnt; var left = spaces*cnt - $this.width()/2; $this.css('left',left+'px'); $this.stop().animate({'bottom':'0px'},500); }).unbind('click').bind('click',spreadPictures); //also rotate each picture of an album with a random number of degrees $images.each(function(){ var $this = $(this); var r = Math.floor(Math.random()*41)-20; $this.transform({'rotate' : r + 'deg'}); }); } }).attr('src', $image.attr('src')); });
The spreadPictures function will do a similar thing: it will move the chosen album to the left and spread all the thumbnails:
function spreadPictures(){ var $album = $(this); //track which album is opened album = $album.index(); //hide all the other albums $albums.not($album).stop().animate({'bottom':'-90px'},300); $album.unbind('click'); //now move the current album to the left //and at the same time spread its images throughout //the window, rotating them randomly, hide the description of the album //store the current left for the reverse operation $album.data('left',$album.css('left')) .stop() .animate({'left':'0px'},500) .find('.descr') .stop() .animate({'bottom':'-30px'},200); var total_pic = $album.find('.content').length; var cnt = 0; //each picture $album.find('.content') .each(function(){ var $content = $(this); ++cnt; //window width var w_width = $(window).width(); var spaces = w_width/(total_pic+1); var left = (spaces*cnt) - (140/2); var r = Math.floor(Math.random()*41)-20; //var r = Math.floor(Math.random()*81)-40; $content.stop().animate({'left':left+'px'},500,function(){ $(this).unbind('click') .bind('click',showImage) .unbind('mouseenter') .bind('mouseenter',upImage) .unbind('mouseleave') .bind('mouseleave',downImage); }).find('img') .stop() .animate({'rotate': r+'deg'},300); $back.stop().animate({'left':'0px'},300); }); }
Now, we will define what happens when we click on the item to go back to the albums view. We will animate the album to its initial left position and slide the other albums up again. If the user was viewing a full image, we will make it slide up, out of the window (hideCurrentPicture):
$back.bind('click',function(){ $back.stop().animate({'left':'-100px'},300); hideNavigation(); //there's a picture being displayed //lets slide the current one up if(current != -1){ hideCurrentPicture(); } var $current_album = $('#pp_thumbContainer div.album:nth-child('+parseInt(album+1)+')'); $current_album.stop() .animate({'left':$current_album.data('left')},500) .find('.descr') .stop() .animate({'bottom':'0px'},500); $current_album.unbind('click') .bind('click',spreadPictures); $current_album.find('.content') .each(function(){ var $content = $(this); $content.unbind('mouseenter mouseleave click'); $content.stop().animate({'left':'0px'},500); }); $albums.not($current_album).stop().animate({'bottom':'0px'},500); });
The next function, called showImage, will display the full image by sliding it in from the bottom. If there was another image being shown, we will slide that one up. For centering the preview, we need to set its negative margins according to the width and height of the image. We will also rotate the preview randomly:
function showImage(nav){ if(nav == 1){ //reached the first one if(current==0){ return; } var $content = $('#pp_thumbContainer div.album:nth-child('+parseInt(album+1)+')') .find('.content:nth-child('+parseInt(current)+')'); //reached the last one if($content.length==0){ current-=2; return; } } else var $content = $(this); //show ajax loading image $loader.show(); //there's a picture being displayed //lets slide the current one up if(current != -1){ hideCurrentPicture(); } current = $content.index(); var $thumb = $content.find('img'); var imgL_source = $thumb.attr('alt'); var imgL_description = $thumb.next().html(); //preload the large image to show $('<img style=""/>').load(function(){ var $imgL = $(this); //resize the image based on the windows size resize($imgL); //create an element to include the large image //and its description var $preview = $('<div />',{ 'id' : 'pp_preview', 'className' : 'pp_preview', 'html' : '<div class="pp_descr"><span>'+imgL_description+'</span></div>', 'style' : 'visibility:hidden;' }); $preview.prepend($imgL); $('#pp_gallery').prepend($preview); var largeW = $imgL.width()+20; var largeH = $imgL.height()+10+45; //change the properties of the wrapping div //to fit the large image sizes $preview.css({ 'width' :largeW+'px', 'height' :largeH+'px', 'marginTop' :-largeH/2-20+'px', 'marginLeft' :-largeW/2+'px', 'visibility' :'visible' }); Cufon.replace('.pp_descr'); //show navigation showNavigation(); //hide the ajax image loading $loader.hide(); //slide up (also rotating) the large image var r = Math.floor(Math.random()*41)-20; if(ie) var param = { 'top':'50%' }; else var param = { 'top':'50%', 'rotate': r+'deg' }; $preview.stop().animate(param,500); }).error(function(){ //error loading image. //Maybe show a message : 'no preview available' }).attr('src',imgL_source); }
The next two functions handle the navigation through the images:
//click next image $next.bind('click',function(){ current+=2; showImage(1); }); //click previous image $prev.bind('click',function(){ showImage(1); });
The function hideCurrentPicture will make the preview slide up. We will not use any rotation animation if the browser is IE:
//slides up the current picture function hideCurrentPicture(){ current = -1; var r = Math.floor(Math.random()*41)-20; if(ie) var param = { 'top':'-100%' }; else var param = { 'top':'-100%', 'rotate': r+'deg' }; $('#pp_preview').stop() .animate(param,500,function(){ $(this).remove(); }); }
The showNavigation and hideNavigation functions will take care of showing and hiding the navigation items:
//shows the navigation buttons function showNavigation(){ $next.stop().animate({'right':'0px'},100); $prev.stop().animate({'left':'0px'},100); } //hides the navigation buttons function hideNavigation(){ $next.stop().animate({'right':'-40px'},300); $prev.stop().animate({'left':'-40px'},300); }
When hovering over a thumbnail, we want it to move up a little and rotate slightly:
function upImage(){ var $content = $(this); $content.stop().animate({ 'marginTop' : '-70px' },400).find('img') .stop() .animate({'rotate': '0deg'},400); }
When the mouse leaves the currently hovered thumbnail, we want it to drop back, rotating randomly again:
function downImage(){ var $content = $(this); var r = Math.floor(Math.random()*41)-20; $content.stop().animate({ 'marginTop' : '0px' },400).find('img').stop().animate({'rotate': r + 'deg'},400); }
And finally, we will use a resize function to fit the full image into the window:
function resize($image){ var widthMargin = 50 var heightMargin = 200; var windowH = $(window).height()-heightMargin; var windowW = $(window).width()-widthMargin; var theImage = new Image(); theImage.src = $image.attr("src"); var imgwidth = theImage.width; var imgheight = theImage.height; if((imgwidth > windowW)||(imgheight > windowH)){ if(imgwidth > imgheight){ var newwidth = windowW; var ratio = imgwidth / windowW; var newheight = imgheight / ratio; theImage.height = newheight; theImage.width= newwidth; if(newheight>windowH){ var newnewheight = windowH; var newratio = newheight/windowH; var newnewwidth =newwidth/newratio; theImage.width = newnewwidth; theImage.height= newnewheight; } } else{ var newheight = windowH; var ratio = imgheight / windowH; var newwidth = imgwidth / ratio; theImage.height = newheight; theImage.width= newwidth; if(newwidth>windowW){ var newnewwidth = windowW; var newratio = newwidth/windowW; var newnewheight =newheight/newratio; theImage.height = newnewheight; theImage.width= newnewwidth; } } } $image.css({'width':theImage.width+'px','height':theImage.height+'px'}); }
Since we cufonize the description, we need to add the following lines to the head of the HTML:
<script src="js/cufon-yui.js" type="text/javascript"></script> <script src="js/Note_this_400.font.js" type="text/javascript"></script>
You can find the beautiful “Note This” font on www.fontsquirrel.com.
And that’s it! We hope you enjoyed this tutorial and find it useful!!
Great post !!!!!!!!!!
Thanx
One more incredible effect showing the power of css/jQuery. And works perfectely in IE!
not working in chrome 7 dev on mac.
css works but mouseover not triggered and click opens stack outside of screen.
Wow, This is AWESOME! Fresh idea… Thanks for sharing.
Wow, really really love it.
not working in chrome on mac
Like already mentioned, doesn’t work in Chrome…
Very nice one. Thanks for sharing.
@ CHUCK It’s working on my macbook pro with chrome.
WoW!
This is brilliant 🙂
I’m on Chrome (Win). It worked beautifully.
I’m planning to use it in my project.
doesnt work on chrome win 7
@MF, @Chuck, @bthaxor could you explain what exactly does not work on Chrome? And which versions of Chrome are you using? Cheers, ML
jQuery place!!!
This is the best website in the review and ideas…
keep post master…hosh!!!
Awesome article..thx. Is there a WordPress plugin for this?
thanks a lot Mary Lou,
excellent tut,
all the best to the Tympanus team!!!
Great article !
What is to update to get it working with more than 6 pics ? …i tried but stays stucked on pic 6…
thanks
Wow! Great effect and probably the best jQuery photo gallery I have come across. I am urrently studying javascript and jQuery, so I am still learning the ropes, but I will definitely play with this one. Thanks for the share!
Bloody brilliant! it works great on chrome and ie. Thanks for sharing!
Hi Mary Lou,
you have always great idea and write tutos and code marvelous.
Just un error, i think, you close a DIV not open in line 3 of HTML Structure.
An other question is :
– why you dont use h1 for div with class descr (name of the Album is important, i think), and use ul and li for the photo and a span balise for photo description…?
Thank again
@Jireck, thank you for your feedback, it’s always good and reasonable! Next time I will not forget about the correct hierarchy… I promise!!! 🙂 Cheers, ML
@Franco, if you want to add more pictures to one album, you simply add them to the folder of the respective album (thumbnail and full image) and add the new content divs with the img element inside. Don’t forget to write the path to the big image into the “alt” attribute. Hope it helps, ML
Hi ML
I think hierarchy html is require for a clean code and SEO.
Thanks Mary lou !
i forgot to update the alt attribute…
great support !
thanks again
Great gallery!
Is it possible to set the gallery within a div with a fixed height and width?
Cheers,
Jason
Quite fantastic! Thanks for the very detailed tutorial, today I learned about rotation and cufon !
Now I have work for weeks 🙂
Keep going!
brilliant, as usual!
Uhhh, another great tut. Where do you get those ideas?
Thank you for sharing! U own
Fake Polaroid looks bad. The navigation is not friendly…. sorry it could be better.
Very nice effect, and it works in IE 🙂 , but best viewed on Chrome I guess. Thanks for the tut! Keep it coming!
Pretty amazing work and really useful. I don’t know if that was my firefox or not but it felt a little slow loading things.
Awesome work! Really cool what JavaScript can do!
This is fantastic!
Could someone give me a hint: How can i make a button out of the current large picture to use it as next-button?
This looks great but is Not working on Chome 7.0 beta
@menecio So Google should better fix that beta bug 🙂
Thanks for this awesome article.
I have a question..
Is there a way to get previous and next buttons for the bottom stack also. This is because, say I have more than 50 photos of the same category. Then is there any way to get some kind of paging on the bottom stack. Or Previous/next kind of thing for this bottom stack.
Regards,
Pawan
Hello,
Really love yours tutos.
But for this one if have a problem, why I only put 12 different photos on each albums ??
Thanks to reply
Victor
PS: Sorry for my poor english but I’m a french guy living on Barcelona !
Hi! Awesome solution for a portfolio. But I have a question since I’m not a JS coder. How can I replace the font?!
Outstanding!!! I must say I’m a fan of your site now.
Hi,
Awesome work! I am using it for my project and it works perfectly. Thanks a lot for this great gallery!
Thank you ,
but ,
How do I set a image of a albums for default display?
doesnt work on chrome 7 – ubuntu GNU/Linux
so Cool
Thanks
I’d like to use the gallery inside the structure of a site i’ve made.
The site has a quite simple structure, header, footer and two columns.
So, try to insert the code inside the the main column content (800x600px) but it doesn’t seems to work.
Could Mary Lou or someone else genlty help me?
Thanx from Italy. Congratulations for all your great jobs.
great job! just one question: How I can put a link in the full image? thanks!
Great very nice effect but can change font to another font
Nice script,
but how to put the text on the top left under the pictures ????
hey.. the script is work great, but.. sometimes lag in each transform..
too bad, its only loader an image…;
anyway can we use blog post to replace the image (display blog post in each frame and preview, not an image)???
thanks
Hi, very nice
A wonderful addition to my website… but it only works on my testing server, and when I upload to the web host server. It Does NOT work in any browser… Why would that be ?
Bizarrely this stops working when I change the album images – even if they are made exactly the same size! what gives?
Hi bikeman, if you have it online somewhere, I could take a look… Did you adapt the HTML code, maybe the naming of the images is not correct? Cheers, ML
I used this in my website gallery, but there is one question I would like to ask, what if I have more gallery folder? what will happen would in automatically create a scrolling something so that all of my folders can be viewed?
hello again
The problem with the images is solved, it had nothing to do with the coding, but with some fierce creatures that wind up everything
The question for the font remains though
Thanks again