Animated Portfolio Gallery with jQuery

Today we will create an animated portfolio gallery with jQuery. The gallery will contain a scroller for thumbnails and a content area where we will display details about the portfolio […]

Today we will create an animated portfolio gallery with jQuery. The gallery will contain a scroller for thumbnails and a content area where we will display details about the portfolio item. The image can be enlarged by clicking on it, making it appear as an overlay.

The idea is to animate the content elements whenever a thumbnail is clicked. We will animate the heading from the top, fade out the previous image and slide the descriptions from the sides.

The great template collection for the demo can be found on DzineBlog.

Let’s start with the HTML structure.

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

First we will create the structure for the content area. For that we will create a main div element with the class “pg_content”. Inside we will place the main heading with the class and id “pg_title”, the preview container with the id “pg_preview” and the description elements with the class “pg_description”. The description divs will also have an individual class, i.e. “pg_desc1” and “pg_desc2”:

<div class="pg_content">
	<div id="pg_title" class="pg_title">
		<h1 style="display:block;top:25px;">
			Shape Company Website Design
		</h1>
		<h1>Summer of Love</h1>
		...
	</div>
	<div id="pg_preview">
		<img class="pg_thumb" style="display:block;z-index:9999;" src="images/medium/1.jpg" alt="images/large/1.jpg"/>
		<img class="pg_thumb" src="images/medium/2.jpg" alt="images/large/2.jpg"/>
		...
	</div>
	<div id="pg_desc1" class="pg_description">
		<div style="display:block;left:250px;">
			<h2>Project Description</h2>
			<p>A description comes here</p>
		</div>
		<div>
			<h2>Project Description</h2>
			<p>A description comes here</p>
		</div>
		...
	</div>
	<div id="pg_desc2" class="pg_description">
		<div style="display:block;left:250px;">
			<h2>Technologies Used</h2>
			<p>A text comes here</p>
		</div>
		<div>
			<h2>Technologies Used</h2>
			<p>A text comes here</p>
		</div>
		...
	</div>
</div>

All the first elements inside of those containers will have a specific inline style, making them visible when we load the page. The rest of the elements will have their style defined in the CSS, i.e. their position when they are hidden.

The thumbnails scroller will have the following structure (based on Manos Malihu’s thumbnail scroller):

<div id="thumbContainter">
	<div id="thumbScroller">
		<div class="container">
			<div class="content">
				<div>
					<a href="#">
						<img src="images/thumbs/1.jpg" alt="" class="thumb" />
					</a>
				</div>
			</div>
			<div class="content">
				...
			</div>
			...
		</div>
	</div>
</div>

And finally, we will add a div for the semi-transparent overlay:

<div id="overlay"></div>

Let’s take a look at the style.

The CSS

We start by resetting the margins and paddings, and by the general body style:

*{
	margin:0;
	padding:0;
}
body{
	background: #564c4c url(../webtreats2.jpg) repeat top left;
	font-family:"Trebuchet MS","Myriad Pro", Helvetica, sans-serif;
	font-size:12px;
	color: #111;
	overflow-x:hidden;
}

This and more beautiful background images can be found on http://webtreats.mysitemyway.com.
The overlay is going to have the following style:

#overlay{
	position:fixed;
	top:0px;
	left:0px;
	width:100%;
	height:100%;
	background: #000;
	display:none;
	opacity:0.9;
}

If you want this to work in IE, please add the filter opacity property.
The style for the thumbnail slider will be the following:

#thumbContainter{
	position:fixed;
	top:0px;
	left:0px;
	bottom:0px;
	margin:0;
	width:175px;
	padding:0 10px;
	background:transparent url(../bg.png) repeat top left;
	border-right:1px solid #f0f0f0;
	-moz-box-shadow:-2px 0px 10px #000 inset;
	-webkit-box-shadow:-2px 0px 10px #000 inset;
	box-shadow:-2px 0px 10px #000 inset;
}
#thumbScroller{
	position:relative;
	height:600px;
	overflow:hidden;
	left:-180px;
}
#thumbScroller .container{
	position:relative;
	top:0;
	float:left;
}
#thumbScroller .content{
	clear:both;
	float:left;
}
#thumbScroller .content div{
	padding:2px;
	height:100%;
	float:left;
}
#thumbScroller .content a{
	outline:none;
}
#thumbScroller img{
	border:5px solid #000;
	-moz-box-shadow:0px 0px 2px #000;
	-webkit-box-shadow:0px 0px 2px #000;
	box-shadow:0px 0px 2px #000;
}

All the elements in the content area will be placed absolutely:

img.pg_thumb,
img#pg_large,
.pg_title h1,
.pg_content .pg_description div
{
	position:absolute;
}

The description divs will be not be displayed initially:

.pg_content .pg_description div{
	display:none;
}

The main heading will have a black patterned background image, just like the main thumbnail container. The position will be set to the values where the element will not be visible, hence we will have a top of -50px:

.pg_title h1{
	display:none;
	left:250px;
	top:-50px;/*25*/
	background:transparent url(../bg.png) repeat top left;
	padding:10px 20px;
	color:#fff;
	font-weight:bold;
}

We will then animate the top to 25px to make the heading visible.

The thumb needs to be hidden, too:

img.pg_thumb{
	display:none;
}

The large preview of the image will be created when we click on a portfolio item in the content area. It will have the highest z-index:

img#pg_large{
	z-index:9999;
}

The style of the thumbnail in the content area and the style of the large preview will be as follows:

img.pg_thumb,
img#pg_large{
	top:90px;
	left:250px;
	padding:10px;
	background:transparent url(../bg.png) repeat top left;
	cursor:pointer;
}

The descriptions and their headlines are going to have the following style:

.pg_description h2{
	color:#000;
	font-size:22px;
	margin-bottom:10px;
	background:transparent url(../bg2.png) repeat top left;
	padding:5px;
}
.pg_description p{
	font-size:14px;
	width:500px;
	padding:10px;
	overflow:hidden;
	text-shadow:0px 0px 1px #fff;
	background:transparent url(../bg.png) repeat top left;
	color:#fff;
}

and finally we need to define the positions of the description divs:

#pg_desc1 div{
	top:420px;
	left:205px;
}
#pg_desc2 div{
	top:560px;
	left:295px;
}

And that was the style. Let’s check out the JavaScript next.

The JavaScript

The main idea is to create the scrollable thumbnails container, and to provide the transitions of the content elements. Also, we need to define what happens when we click on a medium image in the content area to view the large image preview.
First, let’s define some variables:

//index of current item
var current				= 0;
//speeds / ease type for animations
var fadeSpeed			= 400;
var animSpeed			= 600;
var easeType			= 'easeOutCirc';
//caching
var $thumbScroller		= $('#thumbScroller');
var $scrollerContainer	= $thumbScroller.find('.container');
var $scrollerContent	= $thumbScroller.find('.content');
var $pg_title 			= $('#pg_title');
var $pg_preview 		= $('#pg_preview');
var $pg_desc1 			= $('#pg_desc1');
var $pg_desc2 			= $('#pg_desc2');
var $overlay			= $('#overlay');
//number of items
var scrollerContentCnt  = $scrollerContent.length;
var sliderHeight		= $(window).height();
//we will store the total height
//of the scroller container in this variable
var totalContent		= 0;
//one items height
var itemHeight			= 0;

Then, we want to create the scrollable thumbnails container, after all its thumbnail images are loaded:

var cnt		= 0;
$thumbScroller.find('img').each(function(){
	var $img 	= $(this);
	$('').load(function(){
		++cnt;
		if(cnt == scrollerContentCnt){
			//one items height
			itemHeight = $thumbScroller.find('.content:first').height();
			buildScrollableItems();
			//show the scrollable container
			$thumbScroller.stop().animate({
				'left':'0px'
			},animSpeed);
		}
	}).attr('src',$img.attr('src'));
});

When we click on a thumbnail in the scrollable container, we want to display the according content elements. We will use the index of the thumbnail in order to know which title, image and description to show:

$scrollerContent.bind('click',function(e){
	var $this 				= $(this);

	var idx 				= $this.index();
	//if we click on the one shown then return
	if(current==idx) return;

	//if the current image is enlarged,
	//then we will remove it, but before
	//we will animate it just like we will do it with the thumb
	var $pg_large			= $('#pg_large');
	if($pg_large.length > 0){
		$pg_large.animate({
			'left':'350px',
			'opacity':'0'
		},animSpeed,function(){
			$pg_large.remove();
		});
	}

	//get the current and clicked item's elements
	var $currentTitle 		= $pg_title.find('h1:nth-child('+(current+1)+')');
	var $nextTitle 			= $pg_title.find('h1:nth-child('+(idx+1)+')');
	var $currentThumb		= $pg_preview.find('img.pg_thumb:eq('+current+')');
	var $nextThumb			= $pg_preview.find('img.pg_thumb:eq('+idx+')');
	var $currentDesc1 		= $pg_desc1.find('div:nth-child('+(current+1)+')');
	var $nextDesc1 			= $pg_desc1.find('div:nth-child('+(idx+1)+')');
	var $currentDesc2 		= $pg_desc2.find('div:nth-child('+(current+1)+')');
	var $nextDesc2 			= $pg_desc2.find('div:nth-child('+(idx+1)+')');

	//the new current is now the index of the clicked scrollable item
	current		 			= idx;

	//animate the current title up,
	//hide it, and animate the next one down
	$currentTitle.stop().animate({
		'top':'-50px'
	},animSpeed,function(){
		$(this).hide();
		$nextTitle.show().stop().animate({
			'top':'25px'
		},animSpeed);
	});

	//show the next image,
	//animate the current to the left and fade it out
	//so that the next one gets visible
	$nextThumb.show();
	$currentThumb.stop().animate({
		'left': '350px',
		'opacity':'0'
	},animSpeed,function(){
		$(this).hide().css({
			'left'		: '250px',
			'opacity'	: 1,
			'z-index'	: 1
		});
		$nextThumb.css({
			'z-index':9999
		});
	});

	//animate both current descriptions left / right and fade them out
	//fade in and animate the next ones right / left
	$currentDesc1.stop().animate({
		'left':'205px',
		'opacity':'0'
	},animSpeed,function(){
		$(this).hide();
		$nextDesc1.show().stop().animate({
			'left':'250px',
			'opacity':'1'
		},animSpeed);
	});
	$currentDesc2.stop().animate({
		'left':'295px',
		'opacity':'0'
	},animSpeed,function(){
		$(this).hide();
		$nextDesc2.show().stop().animate({
			'left':'250px',
			'opacity':'1'
		},animSpeed);
	});
	e.preventDefault();
});

When we click on a thumbnail in the content area (the medium picture), it will animate to the size of the large image. Then we will load the large image and insert it after the thumbnail. We just need to hide the thumbnail then, in order to display the large image:

$pg_preview.find('.pg_thumb').bind('click',showLargeImage);

//enlarges the thumb
function showLargeImage(){
	//if theres a large one remove
	$('#pg_large').remove();
	var $thumb 		= $(this);
	$thumb.unbind('click');
	var large_src 	= $thumb.attr('alt');

	$overlay.fadeIn(200);
	//animate width to 600px,height to 500px
	$thumb.stop().animate({
		'width'	: '600px',
		'height': '500px'
	},500,function(){
		$('').load(function(){
			var $largeImg = $(this);
			$largeImg.insertAfter($thumb).show();
			$thumb.hide().css({
				'left'		: '250px',
				'opacity'	: 1,
				'z-index'	: 1,
				'width'		: '360px',
				'height'	: '300px'
			});
			//when we click the large image
			//we revert the animation
			$largeImg.bind('click',function(){
				$thumb.show();
				$overlay.fadeOut(200);
				$(this).stop().animate({
					'width'	: '360px',
					'height': '300px'
				},500,function(){
					$(this).remove();
					$thumb.css({'z-index'	: 9999});
					//bind again the click event
					$thumb.bind('click',showLargeImage);
				});
				
			});
		}).attr('src',large_src);
	});
}

When we resize the window, the scroller’s height needs to be adapted to the new window height:

$(window).resize(function() {
	var w_h			= $(window).height();
	$thumbScroller.css('height',w_h);
	sliderHeight	= w_h;
});

And finally, the function for creating the scroller container (adapted from Manos’ script, check it out here: Manos Malihu’s thumbnail scroller)

function buildScrollableItems(){
	totalContent = (scrollerContentCnt-1)*itemHeight;
	$thumbScroller.css('height',sliderHeight)
	.mousemove(function(e){
		if($scrollerContainer.height()>sliderHeight){
			var mouseCoords		= (e.pageY - this.offsetTop);
			var mousePercentY	= mouseCoords/sliderHeight;
			var destY			= -(((totalContent-(sliderHeight-itemHeight))-sliderHeight)*(mousePercentY));
			var thePosA			= mouseCoords-destY;
			var thePosB			= destY-mouseCoords;
			if(mouseCoords==destY)
				$scrollerContainer.stop();
			else if(mouseCoords>destY)
				$scrollerContainer.stop()
				.animate({
					top: -thePosA
				},
				animSpeed,
				easeType);
			else if(mouseCoords

And that’s all! If you want to add some spice by cufonizing the headlines, you can add the following lines to the head of your HTML:

<script src="js/cufon-yui.js" type="text/javascript"></script>
<script src="js/Quicksand_Book_400.font.js" type="text/javascript"></script>
<script type="text/javascript">
	Cufon.replace('h1,h2');
</script>

You can find the beautiful Quicksand and other awesome fonts on www.fontsquirrel.com.

We hope you enjoyed the tutorial and find it useful!

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 in the loop: Get your dose of frontend twice a week

Fresh news, inspo, code demos, and UI animations—zero fluff, all quality. Make your Mondays and Thursdays creative!

Feedback 64

Comments are closed.
  1. I have downloaded your script but in it Left thumbnail panel not displaying can you check it and give me proper download zip then we appreciate it , bye the way gr8 work thanks for sharing..

    • @Meera @Catalin I have updated the ZIP file and now it is working! Sorry for that! Thank you all for your great feedback! Cheers, ML

  2. I am using Chrome and i see basiaclly the same bug i wrote about in one of the other tutorials, full page image gallery tutorial . . .

  3. a little difficult in the right clicking on the scroll boss?
    if we click a picture, how do I return again?
    awesome job…ty \m/

  4. Looks great in Chrome and Safari, but Firefox 4.0b7 for mac is looking completely glitched for me.

    Anybody else getting this?

    Great tutorial though, as usual!

  5. I noticed that you didn’t have a license anywhere on your code. I’m assuming this is free to use?? (Weird question, I know but I need to ask for work. 🙂 )

    Great work by the way.

  6. Thank you all for your great feedback!

    @Mike, yes it is free for personal and commercial use, thanks for asking!

  7. I really liked your work.

    hi just want to ask if i want to add some more thumails and pages wht i hav to do. Plz if u can let me soon it will be great.

  8. Hey,
    Can I just directly use this as an internal static page in my wordpress based website?! A total cipher in web programming…

  9. This gallery is working fine and very good.But in IE7 & IE8 it is not working perfectly.there is no smooth easing.

    Please help me with this problem.

    Thanks in advance.

  10. Its beautiful !! Congratz ! But, its too slow on IE7. Incredibly on ie6 its Ok, but on ie7 its almost stopping.

    • Hello Paulo,
      maybe it’s a problem with Cufón, you can try and remove the scripts from the head. Let me know if it get’s better, hope it helps, cheers, ML

  11. Hello Mary,
    I think the problem occurred because im running it local. I’ve tried remove the Cufon and nothing changed. And again, congratz about your website.

  12. i love this portfolio layout!

    my question is this:

    instead of changing a single image when each thumbnail in the vertical scroller is clicked, would it be possible to change a horizontal scroller for each project?

    i would love to use this on my portfolio site, but would like to exhibit multiple images for each project.

    any suggestions?

  13. stunning… really nice.. but I’m also wondering on how you can make this into a horizontal scroller?

  14. Beautiful portfolio, but its almost stopping in IE8… I try remove the scripts from the head, but nothing changed.. Please, somebody, help me with this problem!

  15. and how can i put the thubs sliders on bottom with a horizontal scroller? With manos code its easy, but in this compilation i didnt found…

  16. how do i show more thumbnails…i wrote all the list in the inside of the code, but it only shows 8 thumbnails…HELP plis

  17. When I tired to add more images, the side bar disrepair, what am I doing wrong?

    I added the image urls to 3 locations on the html page.

    Is any editing needed on the .js files?

  18. First of all I have to say thank you Mary for your free work sharing with the world! 🙂

    And for all of you people as you probably noticed it doesn’t work in IE9. Well here is solution… just chance cufon-yui.js file with this one and then will work just fine also in IE9. Mary used 1.09 and here you have newer version 1.09i to solve the problem!

    http://cufon.shoqolate.com/js/cufon-yui.js?v=1.09i

    Be cool people! 😉

  19. Hi,

    I’m trying to add on more thumbnails on the side bar, but when adding more, the entire bar no longer shows.

    Do I have to edit any other files other then the html? Would I need to edit the javascript files as well?

  20. I really like the gallery and was wondering if there is a way to make the thumbnails scrollable with the mousewheel?

    Without showing the scoll (e.g. not overflow:scroll)