Fresh Sliding Thumbnails Gallery with jQuery and PHP

In this tutorial we are going to create another full page image gallery with a nice thumbnail area that scrolls automatically when moving the mouse. The idea is to allow […]

In this tutorial we are going to create another full page image gallery with a nice thumbnail area that scrolls automatically when moving the mouse. The idea is to allow the user to slightly zoom into the picture by clicking on it. The thumbnails bar slides down and the image resizes according to the screen size.

The scrolling functionality of the thumbnails bar is based on the great tutorial by Andrew Valums: Horizontal Scrolling Menu made with CSS and jQuery.
Additionally, we will be using PHP to get the images and thumbs automatically from the folder structure. The folders will contain album sub-folders and we will add a select option to the gallery which allows to choose an album.

Just like in the Sliding Panel Photo Wall Gallery with jQuery we will be using a resize function for the image displayed.

We will also add an XML file that (optionally) contains the descriptions of the images.

Another neat functionality that we are going to add is the changing cursor: depending in which mode and in which position of the full image we are, the cursor will change to a left or right arrow (for browsing the pictures), or to a plus or minus sign (for zooming in or out).

The beautiful images in the demo are from the Models 1 – Photoshoots album from Vincent Boiteau’s photostream on Flickr.

We also have a static version of the image gallery without the album option. You can find the demo link and the ZIP file in the end of this post.

And don’t forget to check out the tutorial of the  mobile version of this gallery: Awesome Mobile Image Gallery Web App

So, let’s start!

The Folder Structure

Today we will start this tutorial by the folder structure since it is important for our PHP functionality.

The necessary folders for the PHP to work are the images folder and the thumbs folder. They both need to be located in the root folder (where you will have the index.php file).

Whatever album sub-folder will be in the thumbs folder, also needs to be in the images folder. So, if we have thumbs/album1/22.jpg we also need images/album1/22.jpg which will be the full-size image.

With that organization we will be able to automatically display the album thumbnails and create a select box for all albums.

In each album folder of the thumbs we will also have an XML file with the descriptions for the images. We will call that file desc.xml. Adding the description for images is not obligatory, i.e. we will just read the ones that are there. The structure of the XML file will be the following:

<descriptions>
	<image>
		<name>1.jpg</name>
		<text>This is a nice description</text>
	</image>
	<image>
		<name>2.jpg</name>
		<text>red!</text>
	</image>
	<image>
		<name>3.jpg</name>
		<text>another one...</text>
	</image>
	...
</descriptions>

It is important that we name the images in the name tag correctly.
And also, make sure not to have any other files lying around in those folders.

The Markup and PHP

Let’s take a look at the HTML and also the PHP. We have a simple structure that will be dynamically “filled” by our PHP and JavaScript code:

<div class="albumbar">
	<span>Vincent Boiteau's photostream</span>
	<div id="albumSelect" class="albumSelect">
		<ul>
			<!-- We will dynamically generate the items -->
			<?php
				$firstAlbum = '';
				$i=0;
				if(file_exists('images')) {
					$files = array_slice(scandir('images'), 2);
					if(count($files)) {
						natcasesort($files);
						foreach($files as $file) {
							if($file != '.' && $file != '..') {
								if($i===0)
									$firstAlbum = $file;
								else
									echo "<li><a>$file</a></li>";
								++$i;
							}
						}
					}
				}
			?>
		</ul>
		<div class="title down">
			<?php echo $firstAlbum;?>
		</div>
	</div>
</div>
<div id="loading"></div>
<div id="preview">
	<div id="imageWrapper">
	</div>
</div>
<div id="thumbsWrapper">
</div>
<div class="infobar">
	<span id="description"></span>
</div>

The select box items get generated dynamically: we check the sub-folders in the images folder and put all the names in our items. The first album will be “selected” by default.

When we click on one of the items we will call the thumbs.php (inside the ajax folder) from within the JavaScript. We will get back an array (JSON) with all the information that we need to build our thumbnails. Let’s look at that PHP code first and later we will go through the JS:

$album 		= $_GET['album'];
$imagesArr	= array();
$i		= 0;

/* read the descriptions xml file */
if(file_exists('../thumbs/'.$album.'/desc.xml')) {
    $xml = simplexml_load_file('../thumbs/'.$album.'/desc.xml');
}
/* read the images from the album and get the
 * description from the XML file:
 */
if(file_exists('../thumbs/'.$album)) {
    $files = array_slice(scandir('../thumbs/'.$album), 2);
    if(count($files)) {
        foreach($files as $file) {
            if($file != '.' && $file != '..' &&  $file!='desc.xml') {
                if($xml) {
                    $desc = $xml->xpath('image[name="'.$file.'"]/text');
                    $description = $desc[0];
                    if($description=='')
                        $description = '';
                }
                $imagesArr[] = array('src' => 'thumbs/'.$album.'/'.$file,
                    'alt'	=> 'images/'.$album.'/'.$file,
                    'desc'	=> $description);
            }
        }
    }
}
$json 		= $imagesArr;
$encoded 	= json_encode($json);
echo $encoded;
unset($encoded);

So, we basically get all the thumbnails of the requested album and prepare the information for each img element. The final element that we will then add to our HTML will contain an alt attribute with the full image location as value and a title attribute with the description of the regarding picture as value. The description of the image is taken from the XML file we mentioned before. With an xpath expression we get to the node “name” that contains the image name and then we get the text of the description. In the JS we will then say that the description should be the value of the “title” attribute.

Now, let’s take a look at the style.

The CSS

First, we will add some default styling to the body:

body{
    font-family:Verdana;
    text-transform:uppercase;
    color:#fff;
    font-size:10px;
    overflow:hidden;
    background-color:#f9f9f9;
}

The current background color will be almost white but you can try other colors, it looks really wonderful with some!

Let’s style the album bar for the title of the page:

.albumbar{
    height:24px;
    line-height:24px;
    text-align:center;
    position:fixed;
    background-color:#000;
    left:0px;
    width:100%;
    top:0px;
    -moz-box-shadow:-2px 0px 4px #333;
    -webkit-box-shadow:-2px 0px 4px #333;
    box-shadow:-2px 0px 4px #333;
    z-index:11;
}

And also the info bar which will contain the description of each image:

.infobar{
    height:22px;
    line-height:22px;
    text-align:center;
    position:fixed;
    background-color:#000;
    left:0px;
    width:100%;
    bottom:0px;
    -moz-box-shadow:0px -1px 2px #000;
    -webkit-box-shadow:0px -1px 2px #000;
    box-shadow:0px -1px 2px #000;
}
span#description, .albumbar span{
    text-shadow:0px 0px 1px #fff;
    color:#fff;
}
.albumbar span a{
    color:#aaa;
    text-decoration:none;
}
.albumbar span a:hover{
    color:#ddd;
}

The info bar and the album bar will be fixed and located at the top and bottom of the page.
The select box and the inner list will be styled as follows:

.albumSelect{
    height:18px;
    line-height:18px;
    position:absolute;
    right:5px;
    top:2px;
    width:120px;
}
.albumSelect .title{
    color:#f0f0f0;
    z-index:10;
    border:1px solid #444;
    background-color:#555;
    background-repeat:no-repeat;
    background-position:90% 50%;
    cursor:pointer;
    text-align:left;
    text-indent:10px;
    width:100%;
    position:absolute;
    top:0px;
    left:0px;
}

The title div will have a little triangle as background image. We define two classes, up and down, that we will then set dynamically depending on if the album list is expanded or not:

.down{
    background-image:url(../icons/down.png);
}
.up{
    background-image:url(../icons/up.png);
}

The unordered list with all the albums will be styled as follows:

.albumSelect ul {
    list-style:none;
    display:none;
    padding:0px;
    width:100%;
    border:1px solid #444;
    background-color:#555;
    margin:22px 0px 0px 0px;
    -moz-box-shadow:0px 0px 2px #000;
    -webkit-box-shadow:0px 0px 2px #000;
    box-shadow:0px 0px 2px #000;
}
.albumSelect ul li a{
    text-decoration:none;
    cursor:pointer;
    display:block;
    padding:3px 0px;
    color:#ccc;
}
.albumSelect ul li a:hover{
    background-color:#000;
    color:#fff;
}

The list is set to display:none in the beginning since we only want it to appear when the user clicks on the triangle to expand it.

The loading container will be set to appear at the center of the page, with just a little bit more to the top since we have the thumbnails bar appearing sometimes. Setting top to 40% gives us what we need:

#loading{
    display:none;
    width:50px;
    height:50px;
    position:absolute;
    top:40%;
    left:50%;
    margin-left:-24px;
    background:transparent url(../icons/loading.gif) no-repeat top left;
}

To make the thumbs bar scrollable by moving the mouse we need to give it a special style. The thumbsWrapper will be positioned absolutely and occupy the width of the window. We set the vertical overflow to hidden because we don’t want any scroll bar to appear on the right.
The horizontal overflow will be managed in the JavaScript (it will be hidden).

#thumbsWrapper{
    position: absolute;
    width:100%;
    height:102px;
    overflow-y:hidden;
    background-color:#000;
    bottom:0px;
    left:0px;
    border-top:2px solid #000;
}

The thumbsContainer will be the inner div that will have a width equal to the sum of all the thumbnail widths. We will calculate the width dynamically in the JavaScript, so we don’t define it in the class:

#thumbsContainer{
    height:79px;
    display:block;
    margin: 0;
}

The thumbnail images will have the following style:

#thumbsWrapper img{
    float:left;
    margin:2px;
    display:block;
    cursor:pointer;
    opacity:0.4;
    filter:progid:DXImageTransform.Microsoft.Alpha(opacity=40);
}

We give them a low opacity value since we want to add a hover effect.

The imageWrapper that contains the full image has the following style:

#imageWrapper{
    position:relative;
    text-align:center;
    padding-top:30px;
}

We add a top padding because we have the album bar at the top of the page. We don’t want the image to get hidden by it. The margin 0 auto will center the image horizontally:

#imageWrapper img{
    margin:0 auto;
    -moz-box-shadow:2px 2px 10px #111;
    -webkit-box-shadow:2px 2px 10px #111;
    box-shadow:2px 2px 10px #111;
}

We also create a neat box shadow for all modern browsers 🙂
Some of you might wonder why we set text-align center in the imageWrapper if we have the margin in the image. When we make things appear with the fadeIn function in jQuery, the display of the respective element becomes “block”. For that case our “margin:0 auto” will center the image. But for the case when we put the first image initially we need the inline centering property which is to give the parent “text-align:center”.

And finally, we define the classes for the different cursor types:

.cursorRight{
.cursorRight{
    cursor:url("../icons/next.cur"), url("icons/next.cur"), default;
}
.cursorLeft{
    cursor:url("../icons/prev.cur"), url("icons/prev.cur"),  default;
}
.cursorPlus{
    cursor:url("../icons/plus.cur"), url("icons/plus.cur"), default;
}
.cursorMinus{
    cursor:url("../icons/minus.cur"), url("icons/minus.cur"), default;
}

OK, this is basically a hack and not really nice, but the reason for this ugliness is the browsers’ handling. The first url is the path for FireFox, the second one is for IE and the default value needs to be there again for Firefox. Read more about custom cursors and cross-browser compatibility here.

Now, let’s get to the JavaScript.

The JavaScript

Let’s go step by step through the jQuery code. I will not follow the order like it is in the script but by the usage of the functions. I hope that it will be easier to understand like that.
In our $(function() { } we will add the following JavaScript:

/* name of the selected album, in the top right combo box */
    var album	= $('#albumSelect div').html();
    /* mode is small or expanded, depending on the picture size  */
    var mode = 'small';
    /* this is the index of the last clicked picture */
    var current = 0;

So, we will first declare some variables that we will need later and then we call:

 buildThumbs();

The buildThumbs() function is going to get the current album and generate the images with the accoring source and information:

function buildThumbs(){
	current=1;
	$('#imageWrapper').empty();
	$('#loading').show();
	$.get('ajax/thumbs.php?album='+album, function(data) {
		var countImages = data.length;
		var count = 0;
		var $tContainer = $('<div/>',{
			id	: 'thumbsContainer',
			style	: 'visibility:hidden;'
		})
		for(var i = 0; i < countImages; ++i){
			try{
				var description = data[i].desc[0];
			}catch(e){
				description='';
			}
			if(description==undefined)
				description='';
			$('<img title="'+description+'" alt="'+data[i].alt+'" height="75" />').load(function(){
				var $this = $(this);
				$tContainer.append($this);
				++count;
				if(count==1){
					/* load 1 image into container*/
					$('<img id="displayed" style="display:block;" class="cursorPlus"/>').load(function(){
						var $first = $(this);
						$('#loading').hide();
						resize($first,0);
						$('#imageWrapper').append($first);
						$('#description').html($this.attr('title'));
					}).attr('src',$this.attr('alt'));
				}
				if(count == countImages){
					$('#thumbsWrapper').empty().append($tContainer);
					thumbsDim($tContainer);
					makeScrollable($('#thumbsWrapper'),$tContainer,15);
				}
			}).attr('src',data[i].src);
		}
	},'json');
}

As we mentioned before, we will be using the thumbs.php file to get the info we need. When we are done building all the thumb images we append it to the thumbsWrapper and determine the size of the container with thumbsDim (line 36):

/* adjust the size (width) of the scrollable container
- this depends on all its images widths
*/
function thumbsDim($elem){
	var finalW = 0;
	$elem.find('img').each(function(i){
		var $img 		= $(this);
		finalW+=$img.width()+5;
	//plus 5 -> 4 margins + 1 to avoid rounded calculations
	});
	$elem.css('width',finalW+'px').css('visibility','visible');
}

Then we use makeScrollable (line 37) to make the thumbnail container scrollable by mouse move:

//Get our elements for faster access and set overlay width
function makeScrollable($wrapper, $container, contPadding){
	//Get menu width
	var divWidth = $wrapper.width();

	//Remove scrollbars
	$wrapper.css({
		overflow: 'hidden'
	});

	//Find last image container
	var lastLi = $container.find('img:last-child');
	$wrapper.scrollLeft(0);
	//When user move mouse over menu
	$wrapper.unbind('mousemove').bind('mousemove',function(e){

		//As images are loaded ul width increases,
		//so we recalculate it each time
		var ulWidth = lastLi[0].offsetLeft + lastLi.outerWidth() + contPadding;

		var left = (e.pageX - $wrapper.offset().left) * (ulWidth-divWidth) / divWidth;
		$wrapper.scrollLeft(left);
	});
}

The following function takes care of the click event on a thumbnail and also the hover event:

/*
clicking on a thumb loads the image
(alt attribute of the thumb is the source of the large image);
mouseover and mouseout for a nice spotlight hover effect
*/
$('#thumbsContainer img').live('click',function(){
	loadPhoto($(this),'cursorPlus');
}).live('mouseover',function(){
	var $this   = $(this);
	$this.stop().animate({
		'opacity':'1.0'
	},200);
}).live('mouseout',function(){
	var $this   = $(this);
	$this.stop().animate({
		'opacity':'0.4'
	},200);
});

When a thumbnail is clicked we call the function loadPhoto (and we also pass the current cursor mode):

/*
loads a picture into the imageWrapper
the image source is in the thumb's alt attribute
*/
function loadPhoto($thumb,cursorClass){
	current		= $thumb.index()+1;
	$('#imageWrapper').empty();
	$('#loading').show();
	$('<img id="displayed" title="'+$thumb.attr('title')+'" class="'+cursorClass+'" style="display:none;"/>').load(function(){
		var $this = $(this);
		$('#loading').hide();
		resize($this,0);
		if(!$('#imageWrapper').find('img').length){
                  $('#imageWrapper').append($this.fadeIn(1000));
                  $('#description').html($this.attr('title'));
            }
	}).attr('src',$thumb.attr('alt'));
}

When want to adapt the size of the picture when we resize the window:

/* when resizing the window resize the picture */
$(window).bind('resize', function() {
	resize($('#displayed'),0);
});

The resize function is defined as follows:

/* function to resize an image based on the windows width and height */
function resize($image, type){
	var widthMargin     = 10
	var heightMargin    = 0;
	if(mode=='expanded')
		heightMargin = 60;
	else if(mode=='small')
		heightMargin = 150;
	//type 1 is animate, type 0 is normal
	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;
			}
		}
	}
	if((type==1)&&(!$.browser.msie))
		$image.stop(true).animate({
			'width':theImage.width+'px',
			'height':theImage.height+'px'
			},1000);
	else
		$image.css({
			'width':theImage.width+'px',
			'height':theImage.height+'px'
			});
}

The heightMargin depends on the mode we are in: if the thumbnails bar is out, we have less space so we reduce the allowed height of the image.

The following functions take care of what happens when we select an album:

/* Album combo events to open, close,
and select an album from the combo
*/
$('#albumSelect div').bind('click',function(){
	var $this = $(this);
	if($this.is('.up'))
		closeAlbumCombo();
	else if($this.is('.down'))
		openAlbumCombo();
});
$('#albumSelect ul > li').bind('click',function(){
	var $this 	= $(this);
	album 		= $this.find('a').html();
	buildThumbs();
	var $combo = $('#albumSelect div');
	$this.find('a').html($combo.html());
	$combo.html(album);
	closeAlbumCombo();
	orderCombo($this.parent());
});

And these are the three functions taking care of our self made combo box:

//functions to control the albums combos
function closeAlbumCombo(){
	var $combo = $('#albumSelect div');
	$combo.addClass('down').removeClass('up');
	$combo.prev().hide();
}
function openAlbumCombo(){
	var $combo = $('#albumSelect div');
	$combo.addClass('up').removeClass('down');
	$combo.prev().show();
}
function orderCombo($ul){
	var items = $ul.find('li').get();
	items.sort(function(a,b){
		var keyA = $(a).text();
		var keyB = $(b).text();

		if (keyA < keyB) return -1;
		if (keyA > keyB) return 1;
		return 0;
	});
	$.each(items, function(i, li){
		$ul.append(li);
	});
}

Now we define what happens when we hover over the main image or when we click on it. Depending on where we hover over the image, we want a certain cursor to appear. For that we check where we are with the mouse and apply the regarding class to the image:

/*
when hovering the main image change the mouse icons (left,right,plus,minus)
also when clicking on the image, expand it or make it smaller depending on the mode
*/
$('#displayed').live('mousemove',function(e){
	var $this 	= $(this);
	var imageWidth 	= parseFloat($this.css('width'),10);

	var x = e.pageX - $this.offset().left;
	if(x<(imageWidth/3))
		$this.addClass('cursorLeft')
			 .removeClass('cursorPlus cursorRight cursorMinus');
	else if(x>(2*(imageWidth/3)))
		$this.addClass('cursorRight')
			 .removeClass('cursorPlus cursorLeft cursorMinus');
	else{
		if(mode=='expanded'){
			$this.addClass('cursorMinus')
				 .removeClass('cursorLeft cursorRight cursorPlus');
		}
		else if(mode=='small'){
			$this.addClass('cursorPlus')
				 .removeClass('cursorLeft cursorRight cursorMinus');
		}
	}
}).live('click',function(){
	var $this = $(this);
	if(mode=='expanded' && $this.is('.cursorMinus')){
		mode='small';
		$this.addClass('cursorPlus')
			 .removeClass('cursorLeft cursorRight cursorMinus');
		$('#thumbsWrapper').stop().animate({
			'bottom':'0px'
		},300);
		resize($this,1);
	}
	else if(mode=='small' && $this.is('.cursorPlus')){
		mode='expanded';
		$this.addClass('cursorMinus')
			 .removeClass('cursorLeft cursorRight cursorPlus');
		$('#thumbsWrapper').stop().animate({
			'bottom':'-85px'
		},300);
		resize($this,1);
	}
	else if($this.is('.cursorRight')){
		var $thumb = $('#thumbsContainer img:nth-child('+parseInt(current+1)+')');
		if($thumb.length){
			++current;
			loadPhoto($thumb,'cursorRight');
		}
	}
	else if($this.is('.cursorLeft')){
		var $thumb = $('#thumbsContainer img:nth-child('+parseInt(current-1)+')');
		if($thumb.length){
			--current;
			loadPhoto($thumb,'cursorLeft');
		}
	}
});

When we click on the image, we check which cursor we had, because like that we know which image we have to display next (i.e. the next or the previous one). Our “current” variable helps us keep track of which picture we are currently viewing.

And that’s it! I hope you enjoyed this gigantic tutorial!

Note that in this demo we don’t use very big images, so the “zoom in”/ resize will just show you the full image maximum and never resize the picture beyond its real dimensions. If you use very large images the effect will be a nice experience for users with a large screen.

We also have a static version of this photo gallery without the album functionality. Check out the static demo or download the ZIP file.

We created a mobile version of this gallery: Awesome Mobile Image Gallery Web App

Message from TestkingJoin our online testking N10-004 web designing course to learn how to improve your website using PHP and jQuery. Download testking 70-640 tutorial and testking 220-701 design guide to learn how to create fresh sliding thumbnails gallery with jquery.

Tagged with:

Manoela Ilic

Manoela is the main tinkerer at Codrops. With a background in coding and passion for all things design, she creates web experiments and keeps frontend professionals informed about the latest trends.

Stay up to date with the latest web design and development news and relevant updates from Codrops.

Feedback 142

Comments are closed.
  1. Hi Mary,

    I have problem when opening it using IE 8, the next,prev, and zoom cursor didnt work smoothly and sometime waiting a bit longer to coming in. As well as the zoom is not slow motion like in mozilla or chrome.

    Do you have any Idea to solve it? please help me or give me some clue.

    many thanks

  2. I am having issues with IE8 with this. I am getting a bug that is coming up in the debugger as an invalid argument located within the jquery library. I have no idea what to do haha

  3. Some pictures are not loading. The loading gif appears then it’s disappears like it must do but the picture doesn’t show up . I must click again on the picture to show up.
    What can be the problem?

  4. Hi,
    Very nice thing. Congrats and thanks for those files. I have a question do. Is there a way to build links to a separate galleries instead to the default one? Let’s say I have a main page from wich I want to redirect to separate photo cattegories /ie. portraits, landscapes, …/. Now, using your files, I can direct only to one index page with the default gallery opened – ‘album 1’ and change it from drop down menu later on. What should I do to make links to different albums. Thanks in advance for the responce.

  5. Hi, love the tutorial but I got a problem with this.

    I wanted to change the order of all and put the thumbs to the top which worked fine (if I did not move them in html) but how can I change the place of the big picture (imageWrapper) closer to the description part and animate it resizing from down to top?
    I’m bit lost with this one.

    Thanks for the help in advance.

  6. Full image works now. Problem is that after I have chosen first thumb and get the full image I cannot choose another thumb. Why is that? Is it jquery problem or css?

    Basicly my version has been changed only putting thumbsWrapper and imagewrapper vice versa and removing the plus/minus thing from imageWrapper.

  7. is it possible to customize this tutorial for flickr? so we can pull the sets, and Images using flickr API

    great tutorial, thanks 🙂

  8. Can anyone help me to achieve this by using flickr API, get the sets, Images and everything?

    I would really appreciate any help 🙂

  9. Hi, thanks for great sharing!!!
    I need the names of the gallery starting with recent, then personal, not alphabetical. Is it possible? please, help me! Thanks in advance.
    Paolo

  10. me again…
    I found one bug: on mac platform, Firefox doesn’t show arrows and plus icons on mouseover.
    It is possible to solve it?
    Paolo

  11. Hi Matt, how did you solved hidden cursors on Mozilla Firefox for mac issue? Excuse me but I am a beginner.

  12. Great looking gallery script. I was wondering: is it possible to create custom buttons and assign them to a album? (for instance: a button ‘landscape’ attached to album 3). I tried looking into that, but my php knowlegde is absolutely zero.

  13. Hi,

    Script looks great. But it doesn’t show any images when javascript is disabled.

    In my opinion, I feel that any gallery script should at least show some images with javascript disabled.

    Also, the thumbs cant be selected with the keyboard.

    I think those 2 things are a stumbling block towards making this accessible.

    I’ve also not tested this on mobile devices however.

    Do you have any plans to make the gallery usable without javascript and usable with the keyboard?

    🙂

  14. What would be the best way to have the site, preload the full-res images after the thumbnails are loaded?

  15. I have added this wonderful script to my site, but the thumbnails do not show. When i test it offline it works fine but online the thumbnails don’t load and can only view one image.

  16. Hi, The code is working fine, but i need to fix the order of the thumbnail image (neglecting the alphabetical order). Is this possible. I am struck with this for the past 2 days. Please can any one help me in this.

  17. wow ! this is so creative and wonderful design. Thanks a lot for your effort and time you spent on such a wonderful design and script !

    you like people enrich the design capabilities on web !

  18. I am trying to build a photography website based on this. I have everything working on my test server, but I wanted to incorporate a slideshow function in to it (so every 5 secs I get a new image). Any ideas on how I would do that?

  19. Hi

    Thanks a lot for your great gallery !
    I’m trying to use it for a website but i have a little problem.

    I would like to put a div with text and pics on the right of the page but when i click on an image to zoom it, this div goes down but i want it to be fixed 🙁

    Any ideas on how I would do that?

    Thank you very much!

  20. This gallery is great and I’d really like to use it to update my photography site – but can anyone help me alter the code to display ‘old style’ inks rather than a drop down menu for selecting the albums ?

    Thanks : )

  21. Hi, awesome gallery just what i was looking for just that i need the albums to have categories. how can i achieve that?? Please please do respond.

    Kind Regards.

  22. I would like the pictures to change instead of having it just sit there. How do I modify the code to do that ?

  23. Is there any way to change the ‘default’ album that gets loaded to let’s say…the 3rd album? I’m trying to link to specific albums from external pages, but haven’t figured out how to change the ‘default’ album.

    Also, any ideas on what needs to be changed in jquery.gallery.js file so the albums aren’t listed alphabetically?

    Thanks!

  24. Hi There,

    I’m running into an issue on the static version where only the first two thumbs are visible in the bottom when using Chrome or Safari. Everything looks fine on Firefox and IE. The demo also has this same issue in static mode.

    Any suggestions?

  25. thank’s for beatiful script end sorry for my very bad english.
    i was implmented this in a cms, is necessary wath the path are not “images” but “images/images” to permit the upload in admin area.
    i have changed this: $files = array_slice(scandir(‘images/images’), 2);
    in the index.php

    and this: /* read the descriptions xml file */
    if(file_exists(‘../images/thumbs/’.$album.’/desc.xml’)) {
    $xml = simplexml_load_file(‘../images/thumbs/’.$album.’/desc.xml’);
    /*if the desc.xml file doesn’t exist create it with the proper structure, file names, and blank description in the thumbs directory*/
    if(!file_exists(‘../images/thumbs/’.$album)) {
    if (!mkdir(‘../images/thumbs/’.$album, 0755))
    //create the desc.xml
    $File = ‘../images/thumbs/’.$album.’/desc.xml’;
    //Loop through folder and get filenames
    $files = array_slice(scandir(‘../images/images/’.$album), 2);
    /* read the images from the album and get the
    * description from the XML file:
    */
    if(file_exists(‘../images/thumbs/’.$album) && !is_emtpy_dir(‘../images/thumbs/’
    $imagesArr[] = array(‘src’ => ‘images/”thumbs/’.$album.’/’.$file,
    ‘alt’ => ‘images/”images/’.$album.’/’.$file,
    ‘desc’ => $description);
    else{
    $files = array_slice(scandir(‘../images/images/’.$album), 2);
    $imagesArr[] = array(‘src’ => ‘images/”images/’.$album.’/’.$file,
    ‘alt’ => ‘images/”images/’.$album.’/’.$file,
    ‘desc’ => $description);
    in the thumbs.php and thumbs.php.bak

    and this: $.get(‘ajax/images/thumbs.php?album=’+album, function(data) in jquery.gallery.js
    but the script don’t show the images.
    when are my error?
    very thank’s for all replies

  26. Can this be modified to use in a specific width ,not full screen view.I like this gallery I will have to see how I can do this

  27. Excuse me why I get this error?

    Can not find the first album by default, but get this message “. DS_Store”, why?

  28. The thumbnails do not load in a consistent order. I think that AVK posted the same observation. Has anyone been able to resolve this?

    I checked imagesArr as well as the JSON string (generated by thumbs.php) and everything looks to be in the right order.

    If anyone has any ideas on how to have the buildThumbs function in the JavaScript load the thumbnails in the right order (alphabetically like this – abc1.img would come before abc2.img – that would be great.

    Thanks for any help.

    thx

  29. interesting !

    is it possible to create a direct link to a specific gallery ?

    thanks

  30. Can anyone explain why my self-hosted cursor loading is extremely slow in IE7?

    No more support for this script now?

  31. the “fresh sliding gallery” is fantastic.
    is it possible to autoscroll the thumbs and main images? any assistance appreciated

  32. Nice job!.But i find the static version can’t complete load thumbs on Safari when you reload,I want know how can fix it.Thanks!

  33. First of all, thank you Mary Lou for writing this tutorial. Secondly thanks to Shai for his contributions – exactly what I needed. I was faced with doing the same thing as this when I had to create a gallery of over 100 images that had to be on the same page. The jquery plug ins (galleria) just didn’t cut it. This is awesome, very much appreciated all!

  34. Hi as per another post above.. in chrome and safari it only shows the first image in the thumbs.. is there a fix for this.. ? I love this script and it is perfect for what I need if I can get it right.. Thanks Mary… Also if possible could you help with something as I am a bit of a newb.. but I will await the response above as this is more important. Thank you so much :0

  35. is there any way I can build cloud-zoomer into this.. I would like an inner zoom on the image wrapper img so that whatever the main images is at the time, it can be zoomed on hovering over it..

    Just think this will really fit the criteria for what i need but I need some help if anybodys good at java?

  36. hello pierpobaby,

    with some google search you might have found this:

    http://pastie.org/982035

    should I say it’s working perfectly? You have to add timthumb.php to be able to make thumbs if do not exist.

    here:

    <?php
    $album = $_GET['album'];
    $imagesArr = array();
    $i = 0;

    /* read the descriptions xml file */
    if(file_exists('../thumbs/'.$album.'/desc.xml')) {
    $xml = simplexml_load_file('../thumbs/'.$album.'/desc.xml');
    }
    else{
    /*if the desc.xml file doesn't exist create it with the proper structure, file names, and blank description in the thumbs directory*/
    if(!file_exists('../thumbs/'.$album)) {
    if (!mkdir('../thumbs/'.$album, 0755)) {
    die('Failed to create folders…');
    }
    }
    //create the desc.xml
    $File = '../thumbs/'.$album.'/desc.xml';
    $Handle = fopen($File, 'w') or die("can't create file");
    //Loop through folder and get filenames
    $files = array_slice(scandir('../images/'.$album), 2);
    if(count($files)) {
    $Data = "\n”;
    foreach($files as $file) {
    if($file != ‘.’ && $file != ‘..’ && $file!=’desc.xml’) {
    $Data .= ”
    $file

    \n”;
    }
    }
    $Data .= “”;
    }
    fwrite($Handle, $Data);
    fclose($Handle);
    }
    /* read the images from the album and get the
    * description from the XML file:
    */
    if(file_exists(‘../thumbs/’.$album) && !is_emtpy_dir(‘../thumbs/’.$album)) {
    $files = array_slice(scandir(‘../thumbs/’.$album), 2);
    if(count($files)) {
    foreach($files as $file) {
    if($file != ‘.’ && $file != ‘..’ && $file!=’desc.xml’) {
    if($xml) {
    $desc = $xml->xpath(‘image[name=”‘.$file.'”]/text’);
    $description = $desc[0];
    if($description==”)
    $description = ”;
    }
    if(pathinfo($file, PATHINFO_EXTENSION)==’youtube’){
    $youtube_id = substr($file, 0, strrpos($file, ‘.’));
    $imagesArr[] = array(‘src’ => ‘http://img.youtube.com/vi/’.$youtube_id.’/default.jpg’,
    ‘alt’ => ‘http://img.youtube.com/vi/’.$youtube_id.’/hqdefault.jpg’,
    ‘desc’ => $description);
    }
    else{
    if(file_exists(‘../thumbs/’.$album.’/’.$file)){
    $imagesArr[] = array(‘src’ => ‘thumbs/’.$album.’/’.$file,
    ‘alt’ => ‘images/’.$album.’/’.$file,
    ‘desc’ => $description);
    }
    else //In the case that the folder has thumbnails, but for current image there is no thumbnail, use timtumb to generate and cache one
    $imagesArr[] = array(‘src’ => ‘ajax/timthumb.php?src=/images/’.$album.’/’.$file.’&w=100&q=50′,
    ‘alt’ => ‘images/’.$album.’/’.$file,
    ‘desc’ => $description);
    }
    }
    }
    }
    }
    else{
    $files = array_slice(scandir(‘../images/’.$album), 2);

    if(count($files)) {
    foreach($files as $file) {
    if($file != ‘.’ && $file != ‘..’ && $file!=’desc.xml’) {
    if($xml) {
    $desc = $xml->xpath(‘image[name=”‘.$file.'”]/text’);
    $description = $desc[0];
    if($description==”)
    $description = ”;
    }
    //Check if $file is a youtube video, if not, process as normal
    if(pathinfo($file, PATHINFO_EXTENSION)==’youtube’){
    $youtube_id = substr($file, 0, strrpos($file, ‘.’));
    $imagesArr[] = array(‘src’ => ‘http://img.youtube.com/vi/’.$youtube_id.’/default.jpg’,
    ‘alt’ => ‘http://img.youtube.com/vi/’.$youtube_id.’/hqdefault.jpg’,
    ‘desc’ => $description);
    }
    else{
    $imagesArr[] = array(‘src’ => ‘ajax/timthumb.php?src=/images/’.$album.’/’.$file.’&w=100&q=50′,
    ‘alt’ => ‘images/’.$album.’/’.$file,
    ‘desc’ => $description);
    }
    }
    }
    }
    }
    $json = $imagesArr;
    $encoded = json_encode($json);
    echo $encoded;
    unset($encoded);

    function is_emtpy_dir($dirname){
    // Returns true if $dirname is a directory and it is empty
    $result=false; // Assume it is not a directory
    if(is_dir($dirname) ){
    $result=true; // It is a directory
    $handle = opendir($dirname);
    while( ( $name = readdir($handle)) !== false){
    if ($name!= “.” && $name !=”..” && $name !=”desc.xml”){
    $result=false; // directory not empty
    break; // no need to test more
    }
    }
    closedir($handle);
    }
    return $result;
    }
    ?>

    http://timthumb.googlecode.com/svn/trunk/timthumb.php

    Enjoy!

  37. don’t know why but it’s not working partialy! i have one gallery that works fine but if i select another one only the first image is shown while the slider at the bottom shows the pictures of the former gallery.

  38. come from China. Thank you for the tutorial, I use IE6 browser fresh slippery thumbnail gallery only a few thumbnail could be seen in the page loads of JACK

  39. Hello.. Can anybody help with implementing Cloud Zoom to work with this script.. I would like to get the innerzoom feature to work on the imagewrapper for this script but need some help with it.. would be greatly appreciated as I think this would be a nice touch to a very good script that you have created Mary.. Thank you ..

  40. Hello,

    so finaly here it is the autoscroll version. Next step is to move this to wordpress, meaning transform the html code to php which is for novice like me a long work to do probably…any help?

    $(function() {
    /* name of the selected album, in the top right combo box */
    var album = $(‘#albumSelect div’).html();
    /* mode is small or expanded, depending on the picture size */
    var mode = ‘small’;
    /* this is the index of the last clicked picture */
    var current = 0;

    //
    // bali, 2011-05-28
    // add slideshow functionality
    //
    var slideshow_enabled = true; // enable or disable the slideshow on page load
    var slideshow_delay = 6000; // slideshow delay in msec.
    var slideshow_extra_delay = 6000; // slideshow additional delay (when directly clicking on a thumbnail), in msec

    var slideshow_timeout=false;
    var slideshow_real_delay=slideshow_delay;

    if(slideshow_enabled){
    var increase_delay_func=function(event){
    slideshow_real_delay=slideshow_delay+slideshow_extra_delay;
    window.clearTimeout(slideshow_timeout);
    slideshow_timeout=window.setTimeout(slideshow_loop,slideshow_real_delay);
    };
    $(“#imageWrapper img”).click(increase_delay_func);
    slideshow_timeout=window.setTimeout(slideshow_loop,slideshow_real_delay);
    }

    function slideshow_loop(){
    slideshow_real_delay=slideshow_delay;
    var $thumb = $(‘#thumbsContainer img:nth-child(‘+parseInt(current+1)+’)’);
    if($thumb.length){
    ++current;
    loadPhoto($thumb,’cursorRight’);
    }
    else
    {
    current = 0; /* restart from the start */
    loadPhoto($thumb,’cursorRight’);
    }
    if(current > 0)
    {

    slideshow_timeout=window.setTimeout(slideshow_loop, slideshow_real_delay);
    }
    else
    {
    var slideshow_real_delay = 0; /* no needs to wait */
    slideshow_timeout=window.setTimeout(slideshow_loop, slideshow_real_delay);
    }
    }

    /* first, let’s build the thumbs for the selected album */
    buildThumbs();

    /*
    clicking on a thumb loads the image
    (alt attribute of the thumb is the source of the large image);
    mouseover and mouseout for a nice spotlight hover effect
    */
    $(‘#thumbsContainer img’).live(‘click’,function(){
    loadPhoto($(this),’cursorPlus’);
    }).live(‘mouseover’,function(){
    var $this = $(this);
    $this.stop().animate({
    ‘opacity’:’1.0′
    },200);
    }).live(‘mouseout’,function(){
    var $this = $(this);
    $this.stop().animate({
    ‘opacity’:’0.4′
    },200);
    });
    /* when resizing the window resize the picture */
    $(window).bind(‘resize’, function() {
    resize($(‘#displayed’),0);
    });
    /* Album Combo Events to open, close, and select an album from the combo */
    $(‘#albumSelect div’).bind(‘click’,function(){
    var $this = $(this);
    if($this.is(‘.up’))
    closeAlbumCombo();
    else if($this.is(‘.down’))
    openAlbumCombo();
    });
    $(‘#albumSelect ul > li’).bind(‘click’,function(){
    var $this = $(this);
    album = $this.find(‘a’).html();
    buildThumbs();
    var $combo = $(‘#albumSelect div’);
    $this.find(‘a’).html($combo.html());
    $combo.html(album);
    closeAlbumCombo();
    orderCombo($this.parent());
    });
    /*
    when hovering the main image change the mouse icons (left,right,plus,minus)
    also when clicking on the image, expand it or make it smaller depending on the mode
    */
    $(‘#displayed’).live(‘mousemove’,function(e){
    var $this = $(this);
    var imageWidth = parseFloat($this.css(‘width’),10);

    var x = e.pageX – $this.offset().left;
    if(x(2*(imageWidth/3)))
    $this.addClass(‘cursorRight’)
    .removeClass(‘cursorPlus cursorLeft cursorMinus’);
    else{
    if(mode==’expanded’){
    $this.addClass(‘cursorMinus’)
    .removeClass(‘cursorLeft cursorRight cursorPlus’);
    }
    else if(mode==’small’){
    $this.addClass(‘cursorPlus’)
    .removeClass(‘cursorLeft cursorRight cursorMinus’);
    }
    }
    }).live(‘click’,function(){
    var $this = $(this);
    if(mode==’expanded’ && $this.is(‘.cursorMinus’)){
    mode=’small’;
    $this.addClass(‘cursorPlus’)
    .removeClass(‘cursorLeft cursorRight cursorMinus’);
    $(‘#thumbsWrapper’).stop().animate({
    ‘bottom’:’0px’
    },300);
    resize($this,1);
    }
    else if(mode==’small’ && $this.is(‘.cursorPlus’)){
    mode=’expanded’;
    $this.addClass(‘cursorMinus’)
    .removeClass(‘cursorLeft cursorRight cursorPlus’);
    $(‘#thumbsWrapper’).stop().animate({
    ‘bottom’:’-85px’
    },300);
    resize($this,1);
    }
    else if($this.is(‘.cursorRight’)){

    var $thumb = $(‘#thumbsContainer img:nth-child(‘+parseInt(current+1)+’)’);
    if($thumb.length){
    ++current;
    loadPhoto($thumb,’cursorRight’);
    }
    }
    else if($this.is(‘.cursorLeft’)){
    var $thumb = $(‘#thumbsContainer img:nth-child(‘+parseInt(current-1)+’)’);
    if($thumb.length){
    –current;
    loadPhoto($thumb,’cursorLeft’);
    }
    }
    });
    /*
    function to build the thumbs container
    An AJAX request is made to retrieve the
    photo locations of the selected album
    */
    function buildThumbs(){
    current=1;
    $(‘#imageWrapper’).empty();
    $(‘#loading’).show();
    $.get(‘ajax/thumbs.php?album=’+album, function(data) {
    var countImages = data.length;
    var count = 0;
    var $tContainer = $(”,{
    id : ‘thumbsContainer’,
    style : ‘visibility:hidden;’
    })
    for(var i = 0; i < countImages; ++i){
    try{
    var description = data[i].desc[0];
    }catch(e){
    description='';
    }
    if(description==undefined)
    description='';
    $('’).load(function(){
    var $this = $(this);
    $tContainer.append($this);
    ++count;
    if(count==1){
    /* load 1 image into container*/
    $(”).load(function(){
    var $first = $(this);
    $(‘#loading’).hide();
    resize($first,0);
    $(‘#imageWrapper’).append($first);
    $(‘#description’).html($this.attr(‘title’));
    }).attr(‘src’,$this.attr(‘alt’));
    }
    if(count == countImages){
    $(‘#thumbsWrapper’).empty().append($tContainer);
    thumbsDim($tContainer);
    makeScrollable($(‘#thumbsWrapper’),$tContainer,15);
    }
    }).attr(‘src’,data[i].src);
    }
    },’json’);
    }
    /* adjust the size (width) of the scrollable container
    – this depends on all its images widths
    */
    function thumbsDim($elem){
    var finalW = 0;
    $elem.find(‘img’).each(function(i){
    var $img = $(this);
    finalW+=$img.width()+5;
    //plus 5 -> 4 margins + 1 to avoid rounded calculations
    });
    $elem.css(‘width’,finalW+’px’).css(‘visibility’,’visible’);
    }
    /*
    loads a picture into the imageWrapper
    the image source is in the thumb’s alt attribute
    */
    function loadPhoto($thumb,cursorClass){
    current = $thumb.index()+1;
    $(‘#imageWrapper’).empty();
    $(‘#loading’).show();
    $(”).load(function(){
    var $this = $(this);
    $(‘#loading’).hide();
    resize($this,0);
    if(!$(‘#imageWrapper’).find(‘img’).length){
    $(‘#imageWrapper’).append($this.fadeIn(1000));
    $(‘#description’).html($this.attr(‘title’));
    }
    }).attr(‘src’,$thumb.attr(‘alt’));
    }
    //functions to control the albums combos
    function closeAlbumCombo(){
    var $combo = $(‘#albumSelect div’);
    $combo.addClass(‘down’).removeClass(‘up’);
    $combo.prev().hide();
    }
    function openAlbumCombo(){
    var $combo = $(‘#albumSelect div’);
    $combo.addClass(‘up’).removeClass(‘down’);
    $combo.prev().show();
    }
    function orderCombo($ul){
    var items = $ul.find(‘li’).get();
    items.sort(function(a,b){
    var keyA = $(a).text();
    var keyB = $(b).text();

    if (keyA keyB) return 1;
    return 0;
    });
    $.each(items, function(i, li){
    $ul.append(li);
    });
    }
    //Get our elements for faster access and set overlay width
    function makeScrollable($wrapper, $container, contPadding){
    //Get menu width
    var divWidth = $wrapper.width();

    //Remove scrollbars
    $wrapper.css({
    overflow: ‘hidden’
    });

    //Find last image container
    var lastLi = $container.find(‘img:last-child’);
    $wrapper.scrollLeft(0);
    //When user move mouse over menu
    $wrapper.unbind(‘mousemove’).bind(‘mousemove’,function(e){

    //As images are loaded ul width increases,
    //so we recalculate it each time
    var ulWidth = lastLi[0].offsetLeft + lastLi.outerWidth() + contPadding;

    var left = (e.pageX – $wrapper.offset().left) * (ulWidth-divWidth) / divWidth;
    $wrapper.scrollLeft(left);
    });
    }
    /* function to resize an image based on the windows width and height */
    function resize($image, type){
    var widthMargin = 10
    var heightMargin = 0;
    if(mode==’expanded’)
    heightMargin = 10;
    else if(mode==’small’)
    heightMargin = 150;
    //type 1 is animate, type 0 is normal
    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;
    }
    }
    }
    if((type==1)&&(!$.browser.msie))
    $image.stop(true).animate({
    ‘width’:theImage.width+’px’,
    ‘height’:theImage.height+’px’
    },1000);
    else
    $image.css({
    ‘width’:theImage.width+’px’,
    ‘height’:theImage.height+’px’
    });
    }
    });

    The buttons steel block at the end and teh begining but the slide show is working. Will add a full screen version as soon it compatible with wordpress. Enjoy 😉

  41. Hi!
    Im going to use static version of this gallery. But i want to integrate this gallery in wordpress and i dont know how i can use only html-code generated by php, whithout 2 folders. I want use code like this

    It it real?

  42. Oh, my code was deleted.
    I want to use code with image thumbnail and original image, without searching images with same names in /images.
    For example:
    apple-thumb.jpg
    apple.jpg

    What I should change in the JS-code?
    Thank you!

  43. It’s strange, but in Chrome for Ubuntu all thumbnails cannot load and scrolling doesnt work… 🙁

    In Firefox it’s clear.