From our sponsor: Meco is a distraction-free space for reading and discovering newsletters, separate from the inbox.
In this tutorial we are going to create a nice and fresh image gallery. The idea is to show the albums as a slider, and when an album is chosen, we show the images of that album as a beautiful photo stack. In the photo stack view, we can browse through the images by putting the top most image behind all the stack with a slick animation.
We will use jQuery and CSS3 properties for the rotated image effect. We will also use the webkit-box-reflect property in order to mirror the boxes in the album view – check out the demo in Google Chrome or Apple Safari to see this wonderful effect.
We will also use a bit of PHP for getting the images from each album.
So, let’s start!
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
In our HTML structure we will have several elements. The main ones are for the album columns with thumbnail and description, and the preview with the photo stack.
The structure for the album view is going to look as follows:
<div id="ps_slider" class="ps_slider"> <a class="prev disabled"></a> <a class="next disabled"></a> <div id="ps_albums"> <div class="ps_album" style="opacity:0;"> <img src="albums/album1/thumb/thumb.jpg" alt=""/> <div class="ps_desc"> <h2>Album title</h2> <span>Description text</span> </div> </div> <div class="ps_album" style="opacity:0;"> ... </div> ... </div> </div>
The opacity of the album div is going to be 0 in the beginning, we will then use JavaScript to fade in the columns.
The information that we need to provide in the HTML is the location of each album and its thumbnail images. With our little PHP script we will then get all the images from the respective album.
The structure for the dark overlay and the preview with the photo stack is going to look like this:
<div id="ps_overlay" class="ps_overlay" style="display:none;"></div> <a id="ps_close" class="ps_close" style="display:none;"></a> <div id="ps_container" class="ps_container" style="display:none;"> <a id="ps_next_photo" class="ps_next_photo" style="display:none;"></a> </div>
We will dynamically insert the pictures of each album into the ps_container div.
Now, let’s take a look at the PHP.
The PHP
Our PHP file will be called from our JavaScript and we will get the information with AJAX call. The PHP looks as follows:
$location = 'albums'; $album_name = $_GET['album_name']; $files = glob($location . '/' . $album_name . '/*.{jpg,gif,png}', GLOB_BRACE); $encoded = json_encode($files); echo $encoded; unset($encoded);
The CSS
The style for the gallery is going to contain some CSS3 properties and also a Webkit specific property for a mirror effect.
Let’s start by resetting some paddings and margins and defining the general style for the body:
body, ul, li, h1, h2, h3{ margin:0; padding:0; } body{ background:#121212; font-family:Arial, Helvetica, sans-serif; font-size:11px; color:#fff; overflow-x:hidden; }
We will continue with the style of the preview. The overlay is going to have the following style:
.ps_overlay{ z-index:90; background:#111; width:100%; height:100%; position:fixed; top:0px; left:0px; opacity:0.5; }
The container for the photo stack will have a defined height and width. We will position it absolutely and center it with the “50%/negative margins” method:
.ps_container{ width:480px; height:350px; position:absolute; top:50%; margin-top:-175px; left:50%; margin-left:-240px; z-index:100; }
The image will have a thick white border and a nice box shadow. We will center the image relatively to its container but since we will only know the width and height of the images once we dynamically add them, we will set the margins in our JavaScript function:
.ps_container img{ border:10px solid #fff; position:absolute; top:50%; left:50%; -moz-box-shadow:1px 1px 10px #000; -webkit-box-shadow:1px 1px 10px #000; box-shadow:1px 1px 10px #000; }
The close button for the preview is going to be fixed at the top right of the window:
a.ps_close{ background:#000 url(../images/close.png) no-repeat center center; cursor:pointer; width:56px; height:56px; position:fixed; right:10px; top:10px; z-index:1000; -moz-border-radius:10px; -webkit-border-radius:10px; border-radius:10px; opacity:0.6; }
We will show a “next photo” element on top of the current image when the user hovers over it:
a.ps_next_photo{ position:absolute; top:50%; left:50%; width:56px; height:56px; margin:-28px 0 0 -28px; z-index:200; cursor:pointer; background:#000 url(../images/next_photo.png) no-repeat 50% 50%; opacity:0.6; -moz-border-radius:10px; -webkit-border-radius:10px; border-radius:10px; }
The hover classes for the last two elements:
a.ps_next_photo:hover, a.ps_close:hover{ opacity:0.8; }
Now we are going to define the style for the album view and its columns. The slider container will be positioned relatively. With the auto values for the left and right margins we center the container horizontally on the page:
.ps_slider{ width:845px; height:300px; position:relative; margin:110px auto 0px auto; }
The navigation elements are going to have the following style:
.ps_slider a.next, .ps_slider a.prev{ position:absolute; background-color:#000; background-position:center center; background-repeat:no-repeat; border:1px solid #232323; width:20px; height:20px; top:50%; margin-top:-10px; opacity:0.6; -moz-border-radius:5px; -webkit-border-radius:5px; border-radius:5px; cursor:pointer; outline:none; } .ps_slider a.prev{ left:-30px; background-image:url(../images/prev.png); } .ps_slider a.next{ right:-30px; background-image:url(../images/next.png); } .ps_slider a.prev:hover, .ps_slider a.next:hover{ border:1px solid #333; opacity:0.9; }
We also define a class for the navigation elements when they are disabled:
.ps_slider a.disabled, .ps_slider a.disabled:hover{ opacity:0.4; border:1px solid #111; cursor:default; }
Each album column is going to have the following style:
.ps_slider .ps_album{ width:140px; height:310px; padding:10px; background-color:#333; border:1px solid #444; position:absolute; top:0px; text-align:center; cursor:pointer; -moz-box-shadow:1px 1px 4px #000; -webkit-box-shadow:1px 1px 4px #000; box-shadow:1px 1px 4px #000; -webkit-box-reflect: below 5px -webkit-gradient( linear, left top, left bottom, from(transparent), color-stop(0.6, transparent), to(rgb(18, 18, 18)) ); }
The last property is the Webkit box reflex that mirrors the columns. We say that it should mirror the element in a gradient like way.
We add a hover class for the whole column:
.ps_slider .ps_album:hover{ background-color:#383838; }
The thumbnail image and the content of the column are going to have the following styles:
.ps_slider .ps_album img{ height:90px; border:1px solid #444; -moz-box-shadow:1px 1px 4px #000; -webkit-box-shadow:1px 1px 4px #000; box-shadow:1px 1px 4px #000; } .ps_slider .ps_album .ps_desc{ display:block; color:#666; background:#111 url(../images/overlay.png) no-repeat bottom right; height:200px; margin-top:10px; text-align:left; line-height:20px; overflow:hidden; text-overflow:ellipsis; border:1px solid #393939; -moz-box-shadow:0px 0px 2px #000 inset; -webkit-box-shadow:0px 0px 2px #000 inset; box-shadow:0px 0px 2px #000 inset; } .ps_slider .ps_album:hover .ps_desc{ background-image:none; } .ps_slider .ps_album .ps_desc span{ display:block; margin:0px 10px 10px 10px; border-top:1px solid #333; padding-top:5px; } .ps_slider .ps_album .ps_desc h2{ margin:10px 10px 0px 10px; text-align:left; padding-bottom:5px; font-weight:normal; color:#ddd; text-shadow:0px 0px 1px #fff; border-bottom:1px solid #000; }
The loading element will show when we click on an album and all the images get loaded in the preview:
.ps_slider .loading{ background:#121212 url(../images/loading.gif) no-repeat 50% 50%; position:absolute; top:0px; left:0px; width:100%; height:100%; opacity:0.7; }
For the opacity to work in IE you need to add this filter (with the right value):
filter:progid:DXImageTransform.Microsoft.Alpha(opacity=60);
I did not want to uglify the code.
Let’s add the magic!
The JavaScript
First, we need to define some variables that help us control the album navigation and keep some elements. The variable positions saves the left value for each album column. This depends on the width of the column.
/** * navR,navL are flags for controlling the albums navigation * first gives us the position of the album on the left * positions are the left positions for each of the 5 albums displayed at a time */ var navR,navL = false; var first = 1; var positions = { '0' : 0, '1' : 170, '2' : 340, '3' : 510, '4' : 680 } var $ps_albums = $('#ps_albums'); /** * number of albums available */ var elems = $ps_albums.children().length; var $ps_slider = $('#ps_slider');
We are going to give an initial positioning to the album columns:
/** * position all the albums on the right side of the window */ var hiddenRight = $(window).width() - $ps_albums.offset().left; $ps_albums.children('div').css('left',hiddenRight + 'px'); /** * move the first 5 albums to the viewport */ $ps_albums.children('div:lt(5)').each( function(i){ var $elem = $(this); $elem.animate({'left': positions[i] + 'px','opacity':1},800,function(){ if(elems > 5) enableNavRight(); }); } );
For sliding through the albums, we will define what happens when we click on the next or previous button and create two functions for moving left or right:
/** * next album */ $ps_slider.find('.next').bind('click',function(){ if(!$ps_albums.children('div:nth-child('+parseInt(first+5)+')').length || !navR) return; disableNavRight(); disableNavLeft(); moveRight(); }); /** * we move the first album (the one on the left) to the left side of the window * the next 4 albums slide one position, and finally the next one in the list * slides in, to fill the space of the first one */ function moveRight () { var hiddenLeft = $ps_albums.offset().left + 163; var cnt = 0; $ps_albums.children('div:nth-child('+first+')').animate({'left': - hiddenLeft + 'px','opacity':0},500,function(){ var $this = $(this); $ps_albums.children('div').slice(first,parseInt(first+4)).each( function(i){ var $elem = $(this); $elem.animate({'left': positions[i] + 'px'},800,function(){ ++cnt; if(cnt == 4){ $ps_albums.children('div:nth-child('+parseInt(first+5)+')').animate({'left': positions[cnt] + 'px','opacity':1},500,function(){ //$this.hide(); ++first; if(parseInt(first + 4) < elems) enableNavRight(); enableNavLeft(); }); } }); } ); }); } /** * previous album */ $ps_slider.find('.prev').bind('click',function(){ if(first==1 || !navL) return; disableNavRight(); disableNavLeft(); moveLeft(); }); /** * we move the last album (the one on the right) to the right side of the window * the previous 4 albums slide one position, and finally the previous one in the list * slides in, to fill the space of the last one */ function moveLeft () { var hiddenRight = $(window).width() - $ps_albums.offset().left; var cnt = 0; var last= first+4; $ps_albums.children('div:nth-child('+last+')').animate({'left': hiddenRight + 'px','opacity':0},500,function(){ var $this = $(this); $ps_albums.children('div').slice(parseInt(last-5),parseInt(last-1)).each( function(i){ var $elem = $(this); $elem.animate({'left': positions[i+1] + 'px'},800,function(){ ++cnt; if(cnt == 4){ $ps_albums.children('div:nth-child('+parseInt(last-5)+')').animate({'left': positions[0] + 'px','opacity':1},500,function(){ //$this.hide(); --first; enableNavRight(); if(first > 1) enableNavLeft(); }); } }); } ); }); }
The next helper functions deal with disabling and enabling the the navigation items:
/** * disable or enable albums navigation */ function disableNavRight () { navR = false; $ps_slider.find('.next').addClass('disabled'); } function disableNavLeft () { navL = false; $ps_slider.find('.prev').addClass('disabled'); } function enableNavRight () { navR = true; $ps_slider.find('.next').removeClass('disabled'); } function enableNavLeft () { navL = true; $ps_slider.find('.prev').removeClass('disabled'); }
In the next steps we will define what happens when we open an album. We first make the loading element appear and show the preview after we loaded all the images. The information of the source comes from an AJAX call to our PHP class. For the rotation effect, we use the rotate CSS3 property which we restrict to a certain range of degrees, so that we don’t rotate an image crazily:
var $ps_container = $('#ps_container'); var $ps_overlay = $('#ps_overlay'); var $ps_close = $('#ps_close'); /** * when we click on an album, * we load with AJAX the list of pictures for that album. * we randomly rotate them except the last one, which is * the one the User sees first. We also resize and center each image. */ $ps_albums.children('div').bind('click',function(){ var $elem = $(this); var album_name = 'album' + parseInt($elem.index() + 1); var $loading = $('<div />',{className:'loading'}); $elem.append($loading); $ps_container.find('img').remove(); $.get('photostack.php', {album_name:album_name} , function(data) { var items_count = data.length; for(var i = 0; i < items_count; ++i){ var item_source = data[i]; var cnt = 0; $('<img />').load(function(){ var $image = $(this); ++cnt; resizeCenterImage($image); $ps_container.append($image); var r = Math.floor(Math.random()*41)-20; if(cnt < items_count){ $image.css({ '-moz-transform' :'rotate('+r+'deg)', '-webkit-transform' :'rotate('+r+'deg)', 'transform' :'rotate('+r+'deg)' }); } if(cnt == items_count){ $loading.remove(); $ps_container.show(); $ps_close.show(); $ps_overlay.show(); } }).attr('src',item_source); } },'json'); }); /** * when hovering each one of the images, * we show the button to navigate through them */ $ps_container.live('mouseenter',function(){ $('#ps_next_photo').show(); }).live('mouseleave',function(){ $('#ps_next_photo').hide(); });
The nice animation of putting the current image at the back of our stack is defined in the following:
/** * navigate through the images: * the last one (the visible one) becomes the first one. * we also rotate 0 degrees the new visible picture */ $('#ps_next_photo').bind('click',function(){ var $current = $ps_container.find('img:last'); var r = Math.floor(Math.random()*41)-20; var currentPositions = { marginLeft : $current.css('margin-left'), marginTop : $current.css('margin-top') } var $new_current = $current.prev(); $current.animate({ 'marginLeft':'250px', 'marginTop':'-385px' },250,function(){ $(this).insertBefore($ps_container.find('img:first')) .css({ '-moz-transform' :'rotate('+r+'deg)', '-webkit-transform' :'rotate('+r+'deg)', 'transform' :'rotate('+r+'deg)' }) .animate({ 'marginLeft':currentPositions.marginLeft, 'marginTop' :currentPositions.marginTop },250,function(){ $new_current.css({ '-moz-transform' :'rotate(0deg)', '-webkit-transform' :'rotate(0deg)', 'transform' :'rotate(0deg)' }); }); }); });
We animate the top and left margins with certain values that create the “putting back” effect. Since our structure always shows the last image on the top, we will also need to insert the moving image at the beginning, so that it appear as last one in the stack.
Clicking on the close element brings us back to the album view:
/** * close the images view, and go back to albums */ $('#ps_close').bind('click',function(){ $ps_container.hide(); $ps_close.hide(); $ps_overlay.fadeOut(400); });
Our famous resize function resizes the image according to the given container width and height.
In the end of the function we apply the sizes and we also apply the negative margins – remember, that’s the way we can center an absolute positioned element; we defined top and left in the CSS to be 50%, so now we need to pull it to the right place with these margin values.
/** * resize and center the images */ function resizeCenterImage($image){ var theImage = new Image(); theImage.src = $image.attr("src"); var imgwidth = theImage.width; var imgheight = theImage.height; var containerwidth = 460; var containerheight = 330; if(imgwidth > containerwidth){ var newwidth = containerwidth; var ratio = imgwidth / containerwidth; var newheight = imgheight / ratio; if(newheight > containerheight){ var newnewheight = containerheight; var newratio = newheight/containerheight; var newnewwidth =newwidth/newratio; theImage.width = newnewwidth; theImage.height= newnewheight; } else{ theImage.width = newwidth; theImage.height= newheight; } } else if(imgheight > containerheight){ var newheight = containerheight; var ratio = imgheight / containerheight; var newwidth = imgwidth / ratio; if(newwidth > containerwidth){ var newnewwidth = containerwidth; var newratio = newwidth/containerwidth; var newnewheight =newheight/newratio; theImage.height = newnewheight; theImage.width= newnewwidth; } else{ theImage.width = newwidth; theImage.height= newheight; } } $image.css({ 'width' :theImage.width, 'height' :theImage.height, 'margin-top' :-(theImage.height/2)-10+'px', 'margin-left' :-(theImage.width/2)-10+'px' }); } });
And that’s it! We hope you enjoyed this tutorial and find it useful!
P.S. Because of some Webkit properties, this demo is a really nice experience if you use a Webkit browser like Chrome or Safari. It will also be fully functional in all latest browsers like Firefox and IE (without those beautiful properties, though).
Hi
I love this plugin. But i do have a problem on IE 8. It works perfect but ig i had to leave page with the gallery open for more than a couple mintues the images do not load. On firefox it works perfect.
Please help!
can anyone please help me this is important because i am using this for my portfolio 🙁
This is a great plugin thank you very much! I notic on your demo and my site that if click to open an album a second time in IE the loading graphic only shows and the album does not appear.
Hey is there a way to stack 5 on top of 5 and so on?so I can have like 5×3 for 15 showing
Dear Mary Lou,
Your work is brilliant. Congratulations for this post.
Nevertheless, I have some issue with Internet Explorer 8. All the images appear on the left.
Great work.
Had some fun playing around with the code and I’m considering using it in a portfolio – only the shuffling part.
So i stripped it out and changed it so it supportes multiple stacks on a page – which just reacts to clicks.
Also played around with css transitions to make rotations easier – works perfectly in chrome – firefox is not agreeing with me fully just yet and I don’t have the possibility to test ie at the moment.
But all and all great work!
Great job
hello,
i had have the loader trouble aswell,
when i putted on my website.
it’s resoved now.
you need to change(ask your host)the php version who should be a old one.
great job!!
Did anyone tried on IE7?
This is great. Right now it is click once for a new album and one disappear. Wondering is there a way to make all 5 or 4 move to the left and disappear. New 5 or 4 would appear upon click.
thanks for your sharing…
Hi, I desperately need to add caption here 🙁 is it possible? like every photo comes out i need a little div underneath that shows a little bit of text….please help me out, I’m trying to figure out something … 🙁
thanks heaps
Hi Mary Lou:
Great tutorial and great stuff! I’ve been playing with the demo for three days straight trying to get it to work with only 1 album, no shifting – just basically want to click a link and have it offer up the photostack.
Can’t for the life of me figure it out. Can you email me and I’ll explain my problem in detail?
Thanks.
Hi and thanks for sharing, very creative! I’m having the same problems as earlier posts, I’ve uploaded your demo to my server with no changes, but just get the loader. can you help?
Thanks
Hi,
i am hobby photographer and will develop the new website.
I like your PhotoGallery.
My question is: It’s possible to change the album subfolder name from Album1 to for example “Ticino”.
An where i have to modify it ?
Grazie.
Detlev
amazing!
@Detlev (and others): When you add an extra attribute to the div container with classname ‘ps_album’ and use your foldername as the value (manualy in HTML or dynamically via PHP e.g.), you wil be able to get this attribute value in the javascript
eg. HTML:
…
Javascript:
Find the row with ‘var album_name = ‘album’ + parseInt($elem.index() + 1);’ and change this to ‘var album_name = $elem.attr(“title”);’.
When using this, setting the path to the parrentfolder correct in the included php-file (e.g. $location = ‘../files/fotoalbums’;), all folders in the parentfolder will be shown, using your own foldernames.
Sorry, the HTML example disappeared in the post.
Another attempt: add an extra attribute to the div in your HTML with classname ‘ps_album’ (in my case I use ‘title’ as attribute , e.g. ‘title='[your_folder_name]’ ‘, but you can decide witch attribute you want to use.). Important is that the attribute in the javascript correponds to the attribute used in your HTML.
can i use this plugin for comercial ?
and adding simple custom code ?
thanks
When the the album is clicked, it would only show the rotating animation but it would not load the album’s images.
You’re damn good! Those demos are great. I might use one of em! linking it to DB.
Very cool work…we’re having fun with image collecting and sharing too with Scrwall! You can add infinite images to your collages and view by dragging around the screen. Keep up the great work!
noyasystem.com
wordless…! awesome work.. couldn’t believe that its all Non Flash !
Hats Off @mary
IE and firefox will not display the albums…it just has the rotating wheel. I’ve uploaded it to my server, tried changing the address in the photostack.php file and still nothing…any ideas on how to make it work?
and is there a way to use images of different sizes — have the container size dynamically change with the dimensions of the image?
This is a great!!!! However, I’m trying to work with asp.net and it’s not working. I convert the PHP portino to this:
System.Web.Script.Serialization.JavaScriptSerializer serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
string location = @”images\albums”;
string album_name = Request.QueryString[“album_name”];
string[] files = new string[20];
files = Directory.GetFiles(Server.MapPath(string.Format(@”{0}\{1}\”, location, album_name)));
string jsonresult = serializer.Serialize(files);
lt.Text = jsonresult;
where lt is:
Please help.
Thanks,
Having similar issues as mentioned above.
When an album is clicked, a preloader appears
but no photos populate the screen.
Tested other browsers (Chrome, Firefox, etc.)
with identical results.
I’ve copy and pasted the code and left
all file names and paths untouched.
I’m running the page from
my local server without success.
I’ve never run into an issue with my local server (MAMP)
in the past and am running PHP 5.
I’ve also attempted ‘jsonwrapper.php’ work around
Brian suggested, also without much success.
It doesn’t Any suggestions???
I have added new images in the albums folder but the image is not getting loaded in the page.
This doesn’t seem to work for me. I have to say that my HTML code has lots of W3C incompliances.
This brings me a big headache to catch the HTML bugs of my template code.
same issue only loader..
tried it hosting on my webserver
tried on IIS with PHP
tried it on XAMP !
still only the loader runs on !
please help
Greetings Mary;
I really like this gallery, I have implemented a modified version of it my my site. However, I’m kind of new at this type of stuff and have been playing with the code, trying to add key captures to close with ‘escape’ and view the album’s photos with the arrow keys. I don’t seem do be doing it right as it doesn’t capture.
Can you help?
P.S. There is already a thank you to you on the site for the use of this script… The thank you page is an easter egg though. Good hunting!
this does not work, i was able to load the thumbs, but after putting images in the respective albums. it does not work. i just get the load wheel and thats it. no images displayed as a stack. please help
Great work Mary Lou. Can you use different size images with different orientations (vertical and landscape) and aspect ratios?
Thank you 🙂 Great job!
Very cool stuff but I am having some issues in IE8 8.0.6001.18702IS and in compatibility mode as well. The slides won’t have the randomly shuffled effect. They are all on top of each other.
Thank you very much for this fantastic script!
I programmed a gallery package for concrete 5 based on your script.
I improved it a bit by adding some new functions, take a look:
http://die-pferdereha.de/index.php/galerie/
-decrease loading time by loading only 5 (or any other number) of images for full view
-other, more classic overview
-auto scaling images (instead of always 460px)
-tool for adding, sorting and removing folders and images
full view has more functions:
-next image by clicking on image or pressing spacebar or the right key
-close the full view with ESC, too
-image number visible in the bottom left
Maybe you want to improve your script with some of these ideas?
I am looking forward to hearing from you!
You guys keep amazes me again and again…keep up the good work