Sweet Thumbnails Preview Gallery

In this tutorial we will create an image gallery with jQuery that shows a preview of each image as a little thumbnail. The idea is to hover over the slider […]

In this tutorial we will create an image gallery with jQuery that shows a preview of each image as a little thumbnail. The idea is to hover over the slider dots and make the regarding thumbnail slide into the previewer. When clicking a slider dot, the full image will slide in from the right or left side, depending on the currently viewed image.

Update: If you are interested in integrating the thumbnails preview slider you might want to check out the new post on how to use only the preview part:
Thumbnails Preview Slider with jQuery

The beautiful images are by talented geishaboy500 and can be found here on his Flickr Photostream.

So, let’s roll!

The Markup

The HTML structure is going to consist of a main container which will have the image wrapper for the big image, the navigation items and the dot list with the thumbnail preview:

<div id="ps_container" class="ps_container">
	<div class="ps_image_wrapper">
		<!-- First initial image -->
		<img src="images/1.jpg" alt="" />
	</div>
	<!-- Navigation items -->
	<div class="ps_next"></div>
	<div class="ps_prev"></div>
	<!-- Dot list with thumbnail preview -->
	<ul class="ps_nav">
		<li class="selected">
			<a href="images/1.jpg" rel="images/thumbs/1.jpg">Image 1</a>
		</li>
		<li>
			<a href="images/2.jpg" rel="images/thumbs/2.jpg">Image 2</a>
		</li>
		...
		<li class="ps_preview">
			<div class="ps_preview_wrapper">
				<!-- Thumbnail comes here -->
			</div>
			<!-- Little triangle -->
			<span></span>
		</li>
	</ul>
</div>

The thumbnail preview element will be a list item in the dot list. It’s going to have a special class since we want to treat this element differently. Each dot list item will contain a link element which will hold the information on the thumbnail image and the big image. Using JavaScript, we will extract that path information from the attributes and create the image elements dynamically.

Let’s take a look at the style.

The CSS

First, we will style the main container. Since our images have a maximum width of 680 pixel and a maximum height of 450 pixel, we will define the following values for the container (leaving some space for the dot list):

.ps_container{
	display:none;
	width:680px;
	height:500px;
	margin:20px auto 0px auto;
	position:relative;
}

Now we will style the wrapper for the full images. Here we really set the exact maximum dimensions and say that the overflow is hidden. We do that because we want to be able to put two images inside of this wrapper but cut off the overflow. In our JS function we will animate the images so that the current one gets revealed.
We will center the wrapper by setting the left and right margins to “auto”:

.ps_image_wrapper{
	width:680px;
	height:450px;
	overflow:hidden;
	position:relative;
	margin:0 auto;
	-moz-box-shadow:0px 0px 5px #999;
	-webkit-box-shadow:0px 0px 5px #999;
	box-shadow:0px 0px 5px #999;
}

The image(s) inside of the wrapper should be of position absolute since we want to animate the left value to slide in the current image and slide out the previous one:

.ps_image_wrapper img{
	position:absolute;
	left:0px;
	top:0px;
}

The navigation elements will have the following style:

.ps_prev,
.ps_next{
	width:30px;
	height:59px;
	position:absolute;
	top:50%;
	margin-top:-40px;
	cursor:pointer;
	opacity:0.5;
}
.ps_prev{
	background:transparent url(../images/prev.png) no-repeat top center;
	left:-50px;
}
.ps_next{
	background:transparent url(../images/next.png) no-repeat top center;
	right:-50px;
}
.ps_prev:hover,
.ps_next:hover{
	opacity:0.9;
}

The dot list with the class “ps_nav” will be placed under the full image and centered by auto margins:

ul.ps_nav{
	list-style:none;
	margin:0;
	padding:0;
	width:170px;
	margin:20px auto;
	position:relative;
}

The dot list elements will float:

ul.ps_nav li{
	float:left;
}

And the inner link elements will get the dot background image, which is a sprites image:

ul.ps_nav li a{
	display:block;
	text-indent:-9000px;
	width:11px;
	height:11px;
	outline:none;
	padding:0px 3px;
	background:transparent url(../images/dot.png) no-repeat top center;
}

On hover we will change the background position to show the other half:

ul.ps_nav li a:hover,ul.ps_nav li.selected a{
	background-position:50% -11px;
}

Our special list element, the one that will have the thumbnail preview, will be of absolute positioning. The top has a negative value, since we want to pull this element up, beyond the list. The left value will be dynamically calculated. -34.5 pixel is the left value for the preview element when we want to show it over the first dot:

ul.ps_nav li.ps_preview{
	display:none;
	width:85px;
	height:91px;
	top:-95px;
	left:-34.5px; /*This we will calculate dynamically*/
	position:absolute;
}

The span will be the little triangle:

ul.ps_nav li.ps_preview span{
	background:transparent url(../images/triangle.png) no-repeat top center;
	width:15px;
	height:6px;
	position:absolute;
	top:85px;
	left:35px;
}

The preview wrapper will function the same way like the full image wrapper. We will hide the overflow:

.ps_preview_wrapper{
	width:75px;
	height:75px;
	border:5px solid #fff;
	overflow:hidden;
	position:relative;
	-moz-box-shadow:0px 0px 5px #999;
	-webkit-box-shadow:0px 0px 5px #999;
	box-shadow:0px 0px 5px #999;
}

And ultimately, we want the thumbnails to be of absolute positioning since we want to animate the left value for the sliding effect:

.ps_preview_wrapper img{
	position:absolute;
	top:0px;
	left:0px;
}

And that’s all the style. Let add the jQuery spice!

The JavaScript

The idea of this gallery is to show little thumbnails when hovering over a dot that represents and image.

When moving the cursor over the dots, we want to create a sliding animation that moves the next currently hovered thumbnail image into place. This will create a great effect, giving the illusion that we have an invisible bar of thumbnail images above the dots and our preview element makes them visible.

We also want the clicked image to show up by “pushing” the current one away, either from the left or from the right side.

Both effects we will achieve by placing the images or thumbs next to each other and animating their left value accordingly.

So, let’s begin by caching some elements:

var $ps_container		= $('#ps_container'),
	$ps_image_wrapper 	= $ps_container.find('.ps_image_wrapper'),
	$ps_next			= $ps_container.find('.ps_next'),
	$ps_prev			= $ps_container.find('.ps_prev'),
	$ps_nav				= $ps_container.find('.ps_nav'),
	$tooltip			= $ps_container.find('.ps_preview'),
	$ps_preview_wrapper = $tooltip.find('.ps_preview_wrapper'),
	$links				= $ps_nav.children('li').not($tooltip),
	total_images		= $links.length,
	currentHovered		= -1,
	current				= 0,
	$loader				= $('#loader');

(The loader element was not mentioned in the HTML structure since we placed it outside of the container. We want to show a loading element until all the images are loaded. In the download file you will be able to see the preload function for the images.)

Now we need to check if the browser is a real one or, for whatever insane reason, a crappy one like, let’s say, IE:

var ie 				= false;
if ($.browser.msie) {
	ie = true; // oh no sweet Jesus
}
if(!ie) // there is a God
	$tooltip.css({
		opacity	: 0
	}).show();

Basically, we want to give the preview element or tooltip the opacity 0 and animate it to 1 when we hover over it. Since in IE it does not help to simply add an opacity filter (elements inside are still shown) we want to use the show/hide instead of animating the opacity. So, we add display:none to the style of the class but take it out if we don’t use IE.

After preloading the images, we will show the container:

/*first preload images (thumbs and large images)*/
var loaded	= 0;
$links.each(function(i){
	var $link 	= $(this);
	$link.find('a').preload({
		onComplete	: function(){
			++loaded;
			if(loaded == total_images){
				//all images preloaded,
				//show ps_container and initialize events
				$loader.hide();
				$ps_container.show();
				//when mouse enters the the dots,
				//show the tooltip,
				//when mouse leaves hide the tooltip,
				//clicking on one will display the respective image
				$links.bind('mouseenter',showTooltip)
					  .bind('mouseleave',hideTooltip)
					  .bind('click',showImage);
				//navigate through the images
				$ps_next.bind('click',nextImage);
				$ps_prev.bind('click',prevImage);
			}
		}
	});
});

The function showTooltip() will show the thumbnails preview and animate it to the right place. It will also slide the thumbnails inside, either to the right or to the left, depending where we are “coming from”:

function showTooltip(){
	var $link			= $(this),
		idx				= $link.index(),
		linkOuterWidth	= $link.outerWidth(),
		//this holds the left value for the next position
		//of the tooltip
		left			= parseFloat(idx * linkOuterWidth) - $tooltip.width()/2 + linkOuterWidth/2,
		//the thumb image source
		$thumb			= $link.find('a').attr('rel'),
		imageLeft;

	//if we are not hovering the current one
	if(currentHovered != idx){
		//check if we will animate left->right or right->left
		if(currentHovered != -1){
			if(currentHovered < idx){
				imageLeft	= 75;
			}
			else{
				imageLeft	= -75;
			}
		}
		currentHovered = idx;

		//the next thumb image to be shown in the tooltip
		var $newImage = $('').css('left','0px')
								   .attr('src',$thumb);

		//if theres more than 1 image
		//(if we would move the mouse too fast it would probably happen)
		//then remove the oldest one (:last)
		if($ps_preview_wrapper.children().length > 1)
			$ps_preview_wrapper.children(':last').remove();

		//prepend the new image
		$ps_preview_wrapper.prepend($newImage);

		var $tooltip_imgs		= $ps_preview_wrapper.children(),
			tooltip_imgs_count	= $tooltip_imgs.length;

		//if theres 2 images on the tooltip
		//animate the current one out, and the new one in
		if(tooltip_imgs_count > 1){
			$tooltip_imgs.eq(tooltip_imgs_count-1)
						 .stop()
						 .animate({
							left:-imageLeft+'px'
						  },150,function(){
								//remove the old one
								$(this).remove();
						  });
			$tooltip_imgs.eq(0)
						 .css('left',imageLeft + 'px')
						 .stop()
						 .animate({
							left:'0px'
						  },150);
		}
	}
	//if we are not using a "browser", we just show the tooltip,
	//otherwise we fade it in
	//
	if(ie)
		$tooltip.css('left',left + 'px').show();
	else
	$tooltip.stop()
			.animate({
				left		: left + 'px',
				opacity		: 1
			},150);
}

The function hideTooltip() simply fades out the thumbnails preview (or hides it if IE):

function hideTooltip(){
	//hide / fade out the tooltip
	if(ie)
		$tooltip.hide();
	else
	$tooltip.stop()
			.animate({
				opacity		: 0
			},150);
}

The following function will show an image in full size and animate the wrapper around to the right size. The new image will “slide into place”:

function showImage(e){
	var $link				= $(this),
		idx					= $link.index(),
		$image				= $link.find('a').attr('href'),
		$currentImage 		= $ps_image_wrapper.find('img'),
		currentImageWidth	= $currentImage.width();

	//if we click the current one return
	if(current == idx) return false;

	//add class selected to the current page / dot
	$links.eq(current).removeClass('selected');
	$link.addClass('selected');

	//the new image element
	var $newImage = $('').css('left',currentImageWidth + 'px')
							   .attr('src',$image);

	//if the wrapper has more than one image, remove oldest
	if($ps_image_wrapper.children().length > 1)
		$ps_image_wrapper.children(':last').remove();

	//prepend the new image
	$ps_image_wrapper.prepend($newImage);

	//the new image width
	//this will be the new width of the ps_image_wrapper
	var newImageWidth	= $newImage.width();

	//check animation direction
	if(current > idx){
		$newImage.css('left',-newImageWidth + 'px');
		currentImageWidth = -newImageWidth;
	}
	current = idx;
	//animate the new width of the ps_image_wrapper
	//(same like new image width)
	$ps_image_wrapper.stop().animate({
		width	: newImageWidth + 'px'
	},350);
	//animate the new image in
	$newImage.stop().animate({
		left	: '0px'
	},350);
	//animate the old image out
	$currentImage.stop().animate({
		left	: -currentImageWidth + 'px'
	},350);

	e.preventDefault();
}

The navigation functions will simply trigger a click event on the dots (which we already took care of in the beginning):

function nextImage(){
	if(current < total_images){ 		$links.eq(current+1).trigger('click'); 	} } function prevImage(){ 	if(current > 0){
		$links.eq(current-1).trigger('click');
	}
}

And that’s all! We hope you enjoyed the sweet thumbnails tutorial and find it useful!

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 128

Comments are closed.
  1. ok … autoplay works fine with

    setInterval( function(){
    $(‘.ps_next’).click();
    },5000 );

    but when reach to last pic then stop, practically this should start from first again, anyone advice on this, please

  2. @andry – the javascript is inside the demo html file. You can make it external if you want by using $(document).ready to execute it.

  3. Hello @all,

    1. Nice work Mary… 🙂

    And my question comes as well to support us with a loop function. Please give a try on that feature.
    Thanks in advance.

    Cheers,
    Dan

  4. Here’s the loop function:

    var gotoLoop = 0;
    function nextImage(){
    $links.eq(current+1).trigger(‘click’);
    if (gotoLoop == 1) {
    $links.eq(0).trigger(‘click’);
    gotoLoop = 0;
    }
    if (current == total_images-1){
    gotoLoop = 1;
    }
    }

    Replace the nextImage() function with the code above!

    Bye
    Jo

  5. Loop Forward, loop Back.

    var gotoLoop = 0;
    var backLoop = 1;

    function nextImage(){
    $links.eq(current+1).trigger(‘click’);
    backLoop = 0;
    if (gotoLoop == 1) {
    $links.eq(0).trigger(‘click’);
    gotoLoop = 0;
    }
    if (current == total_images-1){
    gotoLoop = 1;
    }

    }

    function prevImage(){
    if(current > 0){
    $links.eq(current-1).trigger(‘click’);
    }
    if (backLoop == 1) {
    $links.eq(total_images-1).trigger(‘click’);
    backLoop = 0;
    }

    if (current == 0) {
    backLoop = 1;
    }

    }

  6. I could not get this code to work even when i straight up cut and pasted your source code. I think the problem is that i have a PHP file instead of a html file…Is that relevent? Is their a work around for it?

  7. 1. @ giorgio: Thanks a lot my friend, is working like a charm.

    2. @ harold: you´ll need to change the “(‘click’)” top-semicolons right before and after click. Write them with alt gr+#

  8. Hello Mary Lou,
    Thankyou for the wonderful tutorial.

    One question. I wish to make each image clickable so that when i click on each image it will take me to a certain linked page. Can you please tell me how i may accomplish this.
    Look forward to hearing from you.
    Thankyou

  9. Best I have seen, tested back to IE 6, FF3 – had my web partner test it and he gave the thumbs up on backwards compatibility.

    Rejigged the CSS for IE issues but works GREAT!!!!

  10. @Mary Lou – one question that I hope to modify. I would like to increase the number of dots – I figured it out in the the CSS but my dots are always to the left – can’t seem to get them centered – could you contact me?

  11. I was wondering if it is possible to add captions that slide in with each image. Your help would be greatly appreciated.

    Many thanks,
    Alan

  12. Hey, great post. Just I need one thing,
    How can i put a “description, caption or title”, whatever, in each image, is posible?

    Thanks

  13. AMAZING! BRILLIANT! AWESOME!

    Big thanks to Mustafa & Giorgio for helping me out with the autoplay and loop feature. Both work perfectly.

    Still hoping someone has solved by issue of adding captions to each image. Any one have luck with this, if so please do kindly share with us.

    Many thanks,
    Alan