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

👾 Hey! Looking for the latest in frontend? Twice a week, we'll deliver the freshest frontend news, website inspo, cool code demos, videos and UI animations right to your inbox.

Zero fluff, all quality, to make your Mondays and Thursdays more creative!

Feedback 64

Comments are closed.
  1. hi…when i changed the language in dreamweaver for example french i can’t see this…just engilsh show…how to change…i want to change the language plz help…thanx a lot

  2. I really like it! thank u so much for sharing
    i just have a question o,o can i add more? thumbnails, projects etc.
    i tried testing just copying the ones already there, but they dont appear.

    thanks

  3. This is awesome, just pure awesome…
    Really nice work….

    I seem to be having a problem with IE9 though. Pg_title, description not showing…. Anyone konw hot to fix this……

    Portfolio wonderful though…. 🙂

  4. Hi it is possible to control the open page?
    i mean can i decide wich of the item will be open as first?
    something like :
    “http://tympanus.net/Tutorials/AnimatedPortfolioGallery/index.html#2” will open the second

    thx U
    Simo

  5. Great plugin.. just have one issue with re-sizing the #thumbScroller .container because as soon as i change the height the animation stops, how can i change the height so it still scrolls through all the thumbs, if i just change the height of the #thumbContainter (typo in there by the way) and set it to hidden it scroll down to the last thumbs. I guess it is controlled through the jquery but i just cant figure it out, any help would be great. thanks

  6. Hi there,

    Beautiful work here! Is there a way to have multiple expandable images images?

    Such as I click on the first thumbnail and then 5 or 6 medium sized images appear which can then be clicked so they expand to the large image?