Fullscreen Gallery with Thumbnail Flip

In this tutorial we will create a fullscreen gallery with jQuery. The idea is to have a thumbnail of the currently shown fullscreen image on the side that flips when […]

In this tutorial we will create a fullscreen gallery with jQuery. The idea is to have a thumbnail of the currently shown fullscreen image on the side that flips when navigating through the images. The big image will slide up or down depending where we are navigating to. We will add navigation controls for the mousewheel and for keys. The thumbnail will have a zoom and and a fullscreen option, making the image in the background appear in fullscreen mode or as a complete image, resized to fit in the page.

We will be using Flip!, a jQuery plugin by Lucca Manno that flips elements.

And we will also be using the jQuery Mousewheel Plugin by Brandon Aaron.

The beautiful images are by talented Polina Efremova. Visit her website and check out her profile on Behance.

Let’s get started with the markup!

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 add a loading element that we want to show when the images are being loaded:

<div id="tf_loading" class="tf_loading"></div>

Then, we will create a container for all the images that will be shown in fullscreen:

<div id="tf_bg" class="tf_bg">
	<img src="images/1.jpg" alt="Image 1" longdesc="images/thumbs/1.jpg" />
	<img src="images/2.jpg" alt="Image 2" longdesc="images/thumbs/2.jpg"/>
	<img src="images/3.jpg" alt="Image 3" longdesc="images/thumbs/3.jpg"/>
	<img src="images/4.jpg" alt="Image 4" longdesc="images/thumbs/4.jpg"/>
	<img src="images/5.jpg" alt="Image 5" longdesc="images/thumbs/5.jpg"/>
	<img src="images/6.jpg" alt="Image 6" longdesc="images/thumbs/6.jpg"/>
	<div class="tf_pattern"></div>
</div>

We will use the “longdesc” attribute to indicate the path to the respective thumbnail. The last div element will server as the overlay pattern.

Next, we will add a container for the contents that are shown on the left bottom corner of the screen.

<div id="tf_content_wrapper" class="tf_content_wrapper">
	<div class="tf_content" id="content1" style="display:block;">
		<h2>Dreamer</h2>
		<p>Far far away, behind the word mountains, ... </p>
	</div>
	<div class="tf_content" id="content2">
		...
	</div>
	...
</div>

Each box with a heading will be inside of an element with the class “tf_content”.

The structure for the thumbnail image on the right side of the screen we look as follows:

<div id="tf_thumbs" class="tf_thumbs">
	<span id="tf_zoom" class="tf_zoom"></span>
	<img src="images/thumbs/1.jpg" alt="Thumb1"/>
</div>

The span will either have the class “tf_zoom” or “tf_fullscreen” depending on in which mode we are in.

And finally, we will add some elements for the navigation:

<div id="tf_next" class="tf_next"></div>
<div id="tf_prev" class="tf_prev"></div>

Let’s take a look at the style.

The CSS

After doing a simple reset to the body and html, we will define the style for the full images container:

.tf_bg{
	width:100%;
	height:100%;
	position:fixed;
	top:0px;
	left:0px;
}

The container will be fixed and occupy the whole screen. The images inside will be of absolute positioning and the real width and height will be calculated dynamically in our jQuery function:

.tf_bg img{
	position:absolute;
	top:0px;
	left:0px;
	width:100%;
	z-index: 1;
	display:none;
}

The pattern will as well be absolute and have a repeated background image:

.tf_pattern{
	position:absolute;
	width:100%;
	height:100%;
	background:transparent url(../images/pattern.png) repeat top left;
	z-index:2;
}

We will position the content absolutely, too:

.tf_content{
	position:absolute;
	bottom:50px;
	left:50px;
	z-index:10;
	display:none;
}

The heading of the content will have a different font, that we will embed using the Google Font API. We’ll come back to that later.

.tf_content h2{
	color:#fff;
	font-size:90px;
	padding:0;
	margin:0;
	font-family: 'Dancing Script', arial, serif;
	text-shadow:1px 1px 2px #000;
}

The paragraph of the content will have a repeated image as background and some nice box- and text-shadow:

.tf_content p{
	color:#fff;
	padding:0;
	margin:0;
	background:transparent url(../images/bg_content.png) repeat top left;
	padding:40px;
	width:500px;
	font-family: 'PT Sans Narrow', arial, serif;
	font-size:20px;
	line-height:25px;
	text-transform:uppercase;
	text-shadow:2px 2px 1px #000;
	-moz-box-shadow:1px 1px 5px #202020;
	-webkit-box-shadow:1px 1px 5px #202020;
	box-shadow:1px 1px 5px #202020;
	border:4px solid #fff;
}

The thumbnail container will be positioned in the middle of the screen on the right side. We accomplish that with setting the top to 50% and the top margin to negative half of its height. We will give the container a reflection, that can be seen in Webkit browsers:

.tf_thumbs{
	position:absolute;	
	z-index:12;
	right:50px;
	top:50%;
	margin-top:-79px;
	border:4px solid #fff;
	-moz-box-shadow:1px 1px 5px #202020;
	-webkit-box-shadow:1px 1px 5px #202020;
	box-shadow:1px 1px 5px #202020;	
	-webkit-box-reflect:
		below 5px
		-webkit-gradient(
			linear,
			left top,
			left bottom,
			from(transparent),
			color-stop(0.6, transparent),
			to(rgb(18, 18, 18))
		);
}
.tf_thumbs img{
	display:block;
}

The navigation elements will be placed next to the thumbnail container:

.tf_next,
.tf_prev{
	width:35px;
	height:14px;
	position:absolute;
	top:50%;
	right:320px;
	z-index:100;
	cursor:pointer;
	background:transparent url(../images/nav.png) no-repeat top left;
	opacity:0.5;
}
.tf_next{
	background-position:0px -14px;
	margin-top:80px;
}
.tf_prev{
	background-position:0px 0px;
	margin-top:-55px;
}
.tf_next:hover,
.tf_prev:hover{
	opacity:0.9;
}

The little icons for zoom and fullscreen mode will appear at the right top corner of the thumbnail:

.tf_zoom,
.tf_fullscreen{
	width:20px;
	height:20px;
	position:absolute;
	top:6px;
	right:6px;
	cursor:pointer;
	z-index:100;
	opacity:0.6;
	background:transparent url(../images/icons.png) no-repeat top left;
}
.tf_zoom{
	background-position:0px -20px;
}
.tf_fullscreen{
	background-position:0px 0px;
}
.tf_zoom:hover,
.tf_fullscreen:hover{
	opacity:0.9;
}

The loading element will be positioned in the center of the screen:

.tf_loading{
	position:fixed;
	top:50%;
	left:50%;
	margin:-30px 0px 0px -30px;
	width:60px;
	height:60px;
	background:#fff url(../images/loader.gif) no-repeat center center;
	z-index:999;
	opacity:0.7;
}

And that was all the style. Let’s take a look at the JavaScript.

The JavaScript

First, we will include the following scripts:

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.9/jquery-ui.min.js"></script>
<script type="text/javascript" src="js/jquery.flip.js"></script>
<script type="text/javascript" src="js/jquery-mousewheel-3.0.4/jquery.mousewheel.min.js"></script>

Then, we will add the preload function for the images:

(function($) {
	$.fn.preload = function(options) {
		var opts 	= $.extend({}, $.fn.preload.defaults, options);
		o			= $.meta ? $.extend({}, opts, this.data()) : opts;
		var c		= this.length,
			l		= 0;
		return this.each(function() {
			var $i	= $(this);
			$('<img/>').load(function(i){
				++l;
				if(l == c) o.onComplete();
			}).attr('src',$i.attr('src'));	
		});
	};
	$.fn.preload.defaults = {
		onComplete	: function(){return false;}
	};
})(jQuery);

In our jQuery function, we will first cache some element and define some variables:

var $tf_bg				= $('#tf_bg'),
	$tf_bg_images		= $tf_bg.find('img'),
	$tf_bg_img			= $tf_bg_images.eq(0),
	$tf_thumbs			= $('#tf_thumbs'),
	total				= $tf_bg_images.length,
	current				= 0,
	$tf_content_wrapper	= $('#tf_content_wrapper'),
	$tf_next			= $('#tf_next'),
	$tf_prev			= $('#tf_prev'),
	$tf_loading			= $('#tf_loading');

Then, we will preload the images:

$tf_bg_images.preload({
	onComplete	: function(){
		$tf_loading.hide();
		init();
	}
});

The next function will show the first image and initialize some events:

function init(){
	//get dimentions for the image, based on the windows size
	var dim	= getImageDim($tf_bg_img);
	//set the returned values and show the image
	$tf_bg_img.css({
		width	: dim.width,
		height	: dim.height,
		left	: dim.left,
		top		: dim.top
	}).fadeIn();

	//resizing the window resizes the $tf_bg_img
	$(window).bind('resize',function(){
		var dim	= getImageDim($tf_bg_img);
		$tf_bg_img.css({
			width	: dim.width,
			height	: dim.height,
			left	: dim.left,
			top		: dim.top
		});
	});

	//expand and fit the image to the screen
	$('#tf_zoom').live('click',
	function(){
		if($tf_bg_img.is(':animated'))
			return false;

		var $this	= $(this);
		if($this.hasClass('tf_zoom')){
			resize($tf_bg_img);
			$this.addClass('tf_fullscreen')
			.removeClass('tf_zoom');
		}
		else{
			var dim	= getImageDim($tf_bg_img);
			$tf_bg_img.animate({
				width	: dim.width,
				height	: dim.height,
				top		: dim.top,
				left	: dim.left
			},350);
			$this.addClass('tf_zoom')
			.removeClass('tf_fullscreen');	
		}
	}
	);
	
	//click the arrow down, scrolls down
	$tf_next.bind('click',function(){
		if($tf_bg_img.is(':animated'))
			return false;
		scroll('tb');
	});
	
	//click the arrow up, scrolls up
	$tf_prev.bind('click',function(){
		if($tf_bg_img.is(':animated'))
			return false;
		scroll('bt');
	});
	
	//mousewheel events - down / up button trigger the scroll down / up
	$(document).mousewheel(function(e, delta) {
		if($tf_bg_img.is(':animated'))
			return false;
			
		if(delta > 0)
			scroll('bt');
		else
			scroll('tb');
		return false;
	});
	
	//key events - down / up button trigger the scroll down / up
	$(document).keydown(function(e){
		if($tf_bg_img.is(':animated'))
			return false;
		
		switch(e.which){
			case 38:	
				scroll('bt');
				break;	

			case 40:	
				scroll('tb');
				break;
		}
	});
}

The next function takes care of showing the next or previous image:

function scroll(dir){
	//if dir is "tb" (top -> bottom) increment current, 
	//else if "bt" decrement it
	current	= (dir == 'tb')?current + 1:current - 1;
	
	//we want a circular slideshow, 
	//so we need to check the limits of current
	if(current == total) current = 0;
	else if(current < 0) current = total - 1;
	
	//flip the thumb
	$tf_thumbs.flip({
		direction	: dir,
		speed		: 400,
		onBefore	: function(){
			//the new thumb is set here
			var content	= '';
			content		+='<img src="' + $tf_bg_images.eq(current).attr('longdesc') + '" alt="Thumb' + (current+1) + '"/>';
			$tf_thumbs.html(content);
		}
	});

	//we get the next image
	var $tf_bg_img_next	= $tf_bg_images.eq(current),
	//its dimentions
	dim				= getImageDim($tf_bg_img_next),
	//the top should be one that makes the image out of the viewport
	//the image should be positioned up or down depending on the direction
	top	= (dir == 'tb')?$(window).height() + 'px':-parseFloat(dim.height,10) + 'px';
			
	//set the returned values and show the next image	
	$tf_bg_img_next.css({
		width	: dim.width,
		height	: dim.height,
		left	: dim.left,
		top		: top
	}).show();
		
	//now slide it to the viewport
	$tf_bg_img_next.stop().animate({
		top 	: dim.top
	},1000);
		
	//we want the old image to slide in the same direction, out of the viewport
	var slideTo	= (dir == 'tb')?-$tf_bg_img.height() + 'px':$(window).height() + 'px';
	$tf_bg_img.stop().animate({
		top 	: slideTo
	},1000,function(){
		//hide it
		$(this).hide();
		//the $tf_bg_img is now the shown image
		$tf_bg_img	= $tf_bg_img_next;
		//show the description for the new image
		$tf_content_wrapper.children()
		.eq(current)
		.show();
	});
	//hide the current description	
	$tf_content_wrapper.children(':visible')
	.hide()

}

The resize function will animate the image to a screen fitting size:

function resize($img){
	var w_w	= $(window).width(),
	w_h	= $(window).height(),
	i_w	= $img.width(),
	i_h	= $img.height(),
	r_i	= i_h / i_w,
	new_w,new_h;
	
	if(i_w > i_h){
		new_w	= w_w;
		new_h	= w_w * r_i;
		
		if(new_h > w_h){
			new_h	= w_h;
			new_w	= w_h / r_i;
		}
	}	
	else{
		new_h	= w_w * r_i;
		new_w	= w_w;
	}
	
	$img.animate({
		width	: new_w + 'px',
		height	: new_h + 'px',
		top		: '0px',
		left	: '0px'
	},350);
}

The last function will give us the dimensions of an image and its correct positioning:

function getImageDim($img){
	var w_w	= $(window).width(),
	w_h	= $(window).height(),
	r_w	= w_h / w_w,
	i_w	= $img.width(),
	i_h	= $img.height(),
	r_i	= i_h / i_w,
	new_w,new_h,
	new_left,new_top;
	
	if(r_w > r_i){
		new_h	= w_h;
		new_w	= w_h / r_i;
	}
	else{
		new_h	= w_w * r_i;
		new_w	= w_w;
	}

	return {
		width	: new_w + 'px',
		height	: new_h + 'px',
		left	: (w_w - new_w) / 2 + 'px',
		top		: (w_h - new_h) / 2 + 'px'
	};
}

And that was all the jQuery. Now, we want to embed some fonts. For that we will include the following references in the head of our HTML:

<link href='http://fonts.googleapis.com/css?family=PT+Sans+Narrow' rel='stylesheet' type='text/css' />
<link href='http://fonts.googleapis.com/css?family=Dancing+Script' rel='stylesheet' type='text/css' />

And that’s all! We hope you enjoyed the tutorial and learning something new today!

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 97

Comments are closed.
  1. Bravo, as always! And is it possible to make so, that small preview will show image of the next picture?
    It would be more interactive then…
    Thanks.

  2. i thought that was flash with AS3 or something , amazing .. i would like follow your foot steps Mary Lou

  3. Great Gallery, well done.

    Why not show the next image in thumbnail instead of show the current image ?

  4. Really a great job!

    Just one question :
    How can we remove the shadow of the text (tf_content h2 and p) ?

    (Sorry for my english, i’m french…)

    Thank you !

  5. Hi there!

    Thank you for sharing this great gallery!

    Can I also show images in portrait-oriantation without resize to the full browser-width?
    When I insert portraits, it resizes to the browser width und cuts a lot from top and bottom of the image.
    Is it possible to resize portraits to browser height and not to width?
    Landscape-images should be resized to browser width.

  6. Is there a way to add a menu and have the links jump to the section/div the user requested? This would really complement the scrolling navigation. Thanks!

  7. Thanks for this Mary… you’re like a small river named Duden flows behind the word mountains, far from the countries Vokalia and Consonantia…

  8. @Mary Lou : THANKS A LOT ! you saved me a lot of time ! .JPG instead of .jpg … wouldn’t have thought of that 🙂

  9. Wooowww…that’s so AMAZING…Great Work…permit to Bookmark this blog..I love it…thanx Mary Lou

  10. Thanks for this awesome tutorial. I have 2 questions:

    1)Can i give an ordering to the div’s, how they will displayed? Because it’s not following the order in the HTML.

    2)Is there a way to show the thumbnails, but without the ‘flip’ part? I was looking in the .js, but it didn’t make sense to me!:)

    Thanks again for this awesome tutorial!

  11. Hi!
    I really like this gallery. However I have some problems getting this running on Drupal 7. I already posted on some Drupal related sites, however I didn’t get any response so far. Can someone help me?

  12. This gallery is fantastic,but i don’t undestand how i zoom less the fullscreen but i love it,i want to work whit u.
    Sorry for my bad english i’m italian 🙁

  13. Are there any terms and conditions for using this? I’m very interested in making use of this in creating a WordPress template and I’d love to know if there’s specific terms I must abide to… and it goes without saying, but of course I’ll go ahead and say it, full credit will be given to you. Please email me if you can, sakimaa (A.T) gmail.com – Thanks!

  14. Nice work! One question…

    The large images are pixelated/lo res iF i remove the pattern that overlays them.

    Is there a way to keep them clean, hi-res, and non pixelated after deleting the pattern?

    Thanks

  15. @ Shawn – I think you just use a higher (doubled) resolution image, the script resizes it proper in the viewport.

  16. Hi thanks for share.
    I have two questions,
    1. Can slide to a specific image, I mean i watch the image # 1 and want to see imagee # 5?
    2. How I can remove the circular slideshow? I would like to know how I can see the last image and make do not slide forward and make the image # 1 do not slide backward.

  17. This seems to be fine without the unique ID tags in the tf_content HTML element .
    In other words, is there a a reuirement for id=”content1″ , id=”content2″ …?

    PS: Fantastic tutorial Mary Lou!

  18. I’m trying to integrate with a CMS (Drupal 7) and it is difficult to use “longdesc” for the thumb source.

    As a hack, I have:
    1) changed thumb from .attr(‘longdesc’) to .attr(‘src’), and
    2) Set the CSS for .tf_thumbs img max-width:250px; max-height:180px

    I guess that it would probably be preferable to reference a separate list in images (displayed in the same order). Just wondering, what do you think of my hack and woudl you care to offer an alternative implementation where the navigation perhaps uses a list of thumbnail images (hidden with CSS) instead of longdesc.

  19. anyone else have a problem with images resizing correctly on orientation change on iphones with ios 4.1? 4.2 seems to work correctly.

  20. Hi Mary Lou, very nice gallery. I actually can’t call it gallery, beause i see much more in it. Just wondering, does it take you a lot of work to make an option to link/goto a specific ‘slide’?. Another idea would specify the default slide order (by using the navigation) This way you can have sub-slides.

  21. Hi MaryLou, I’m trying to improve the gallery for my website but, while adding more pictures I can see them the same isn’t for the description in fact it’s impossible to see from the seventh to the last…..have you an hint to solve this problem?
    Thank you very much for the cooperation

  22. What license is this script, can I use for my WordPress template, which will dosepny license gpl

  23. so nice and thanks for sharing

    i have a problem

    i need put the navigation or menu with text or image in page pls help me