Responsive Image Gallery with Thumbnail Carousel

A tutorial on how to create a responsive image gallery with a thumbnail carousel using Elastislide. Inspired by Twitter’s “user gallery” and upon a request to show an integration of Elastislide, we want to implement a responsive gallery that adapts to the view-port width. The gallery will have a view switch that allows to view it with the thumbnail carousel or without. We’ll also add the possibility to navigate with the keyboard.

Today we want to show you how to create a responsive image gallery with a thumbnail carousel using Elastislide. Inspired by Twitter’s “user gallery” and upon a request to show an integration of Elastislide, we want to implement a responsive gallery that adapts to the view-port width. The gallery will have a view switch that allows to view it with the thumbnail carousel or without. We’ll also add the possibility to navigate with the keyboard.

We’ll use the jQuery Touchwipe Plugin that will make it possible to navigate the images by “wiping” on the iPhone and iPad.

The images in the demo are by über-talented Sherman Geronimo-Tan and you can find his Flickr photostream here: Sherman Geronimo-Tan’s Flickr Photostream
The photos are licensed under the Creative Commons Attribution 2.0 Generic (CC BY 2.0) License.

So, let’s do it!

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

For the HTML structure we’ll have a main wrapper with the class “rg-gallery”. We’ll also give it the same ID. In another div with the class “rg-thumbs” we’ll add the structure of the Elastislide carousel:

<div id="rg-gallery" class="rg-gallery">
	<div class="rg-thumbs">
		<!-- Elastislide Carousel Thumbnail Viewer -->
		<div class="es-carousel-wrapper">
			<div class="es-nav">
				<span class="es-nav-prev">Previous</span>
				<span class="es-nav-next">Next</span>
			</div>
			<div class="es-carousel">
				<ul>
					<li>
						<a href="#">
							<img src="images/thumbs/1.jpg" data-large="images/1.jpg" alt="image01" data-description="Some description" />
						</a>
					</li>
					<li>...</li>
				</ul>
			</div>
		</div>
		<!-- End Elastislide Carousel Thumbnail Viewer -->
	</div><!-- rg-thumbs -->
</div><!-- rg-gallery -->

The thumbnails will have two data attributes that we’ll use later in our JavaScript. The “data-large” attribute will have the path to the large image and the “data-description” attribute will contain the caption of the image that we will display under the current large image.

For the structure of the large preview area we will create a jQuery template that we’ll add to the head of our document:

<script id="img-wrapper-tmpl" type="text/x-jquery-tmpl">	
	<div class="rg-image-wrapper">
		{{if itemsCount > 1}}
			<div class="rg-image-nav">
				<a href="#" class="rg-image-nav-prev">Previous Image</a>
				<a href="#" class="rg-image-nav-next">Next Image</a>
			</div>
		{{/if}}
		<div class="rg-image"></div>
		<div class="rg-loading"></div>
		<div class="rg-caption-wrapper">
			<div class="rg-caption" style="display:none;">
				<p></p>
			</div>
		</div>
	</div>
</script>

We are adding a condition that will make sure that the navigation is only shown if there is more than one image. The “rg-image” container will be used to add the large image.

Let’s take a look at the style.

The CSS

Besides adjusting a few values like the padding and the margins of the Elastislide thumbnail carousel, we need to style the resting elements of the gallery.

The “rg-image-wrapper” that you saw in our jQuery template will be of relative position and we’ll add a repeated semi-transparent black background image. The borders will be rounded and we’ll give it a min-height of 20 pixels so that the loading element fits into the container initially when the first image get’s loaded:

.rg-image-wrapper{
	position:relative;
	padding:20px 30px;
	background:transparent url(../images/black.png) repeat top left;
	-moz-border-radius: 10px;
	-webkit-border-radius: 10px;
	border-radius: 10px;
	min-height:20px;
}

The container that we’ll use to add the big image will be relative and have a line-height of 0. By adding text-align “center” we make all inline elements align in the center. But since we’ll not set the image to “display:block”, we need to add a line-height of 0. This will make sure that there is no gap under the image which is an inline-element by default:

.rg-image{
	position:relative;
	text-align:center;
	line-height:0px;
}

By setting the max-width of our large image to 100%, we make sure that it will always stay in the surrounding fluid container. This is very nicely explained in Fluid Images by Ethan Marcotte on A List Apart.
Now, why setting the max-height to 100% as well? We actually don’t need this but if you would like to restrict the size of the preview area you could set a fixed height for the “rg-image” class and the image would fit in it while still resizing when the width of the view-port changes.

.rg-image img{
	max-height:100%;
	max-width:100%;
}

Let’s style the navigation elements. The style of the arrow anchors will be the following:

.rg-image-nav a{
	position:absolute;
	top:0px;
	left:0px;
	background:#000 url(../images/nav.png) no-repeat -20% 50%;
	width:28px;
	height:100%;
	text-indent:-9000px;
	cursor:pointer;
	opacity:0.3;
	outline:none;
	-moz-border-radius: 10px 0px 0px 10px;
	-webkit-border-radius: 10px 0px 0px 10px;
	border-radius: 10px 0px 0px 10px;
}

This is actually the style of the left arrow and now we’ll overwrite some properties for the right arrow:

.rg-image-nav a.rg-image-nav-next{
	right:0px;
	left:auto;
	background-position:115% 50%;
	-moz-border-radius: 0px 10px 10px 0px;
	-webkit-border-radius: 0px 10px 10px 0px;
	border-radius: 0px 10px 10px 0px;
}

Since we already defined the left value for the elements in general we need to set it to auto again if we want to use “right” instead.

On hover we want to make them more opaque:

.rg-image-nav a:hover{
	opacity:0.8;
}

The caption will have the following style:

.rg-caption {
	text-align:center;
	margin-top:15px;
	position:relative;
}
.rg-caption p{
	font-size:11px;
	letter-spacing:2px;
	font-family: 'Trebuchet MS', 'Myriad Pro', Arial, sans-serif;
	line-height:16px;
	padding:0 15px;
	text-transform:uppercase;
}

Now, let’s style the switch options:

.rg-view{
	height:30px;
}
.rg-view a{
	display:block;
	float:right;
	width:16px;
	height:16px;
	margin-right:3px;
	background:#464646 url(../images/views.png) no-repeat top left;
	border:3px solid #464646;
	opacity:0.8;
}
.rg-view a:hover{
	opacity:1.0;
}
.rg-view a.rg-view-full{
	background-position:0px 0px;
}
.rg-view a.rg-view-selected{
	background-color:#6f6f6f;
	border-color:#6f6f6f;
}
.rg-view a.rg-view-thumbs{
	background-position:0px -16px;
}

And finally, we’ll make the loading element appear in the center of the image preview:

.rg-loading{
	width:46px;
	height:46px;
	position:absolute;
	top:50%;
	left:50%;
	background:#000 url(../images/ajax-loader.gif) no-repeat center center;
	margin:-23px 0px 0px -23px;
	z-index:100;
	-moz-border-radius: 10px;
	-webkit-border-radius: 10px;
	border-radius: 10px;
	opacity:0.7;
}

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

The JavaScript

The main idea of the gallery is to make it flexible, so partly we have achieved that by our style: the large image will adjust to the container. For making the thumbnail carousel responsive, we’ll use Elastislide, our previous plugin.
First, lets define some variables:

	// gallery container
var $rgGallery			= $('#rg-gallery'),
	// carousel container
	$esCarousel			= $rgGallery.find('div.es-carousel-wrapper'),
	// the carousel items
	$items				= $esCarousel.find('ul > li'),
	// total number of items
	itemsCount			= $items.length;

Then we’ll define our gallery function:

	Gallery				= (function() {
		//gallery function
	})();

Gallery.init();

Here we’ll define some variables for the current image, the mode and a variable for controlling if an image is being loaded. Then we’ll call some of our functions that are following below:

var current			= 0, 
	// mode : carousel || fullview
	mode 			= 'carousel',
	// control if one image is being loaded
	anim			= false,
	init			= function() {
		
		// (not necessary) preloading the images here...
		$items.add('<img src="images/ajax-loader.gif"/><img src="images/black.png"/>').imagesLoaded( function() {
			// add options
			_addViewModes();
			
			// add large image wrapper
			_addImageWrapper();
			
			// show first image
			_showImage( $items.eq( current ) );
		});
		
		// initialize the carousel
		_initCarousel();
		
	},

We need to call the Elastislide plugin:

_initCarousel	= function() {
	$esCarousel.show().elastislide({
		imageW 	: 65,
		onClick	: function( $item ) {
			if( anim ) return false;
			anim	= true;
			// on click show image
			_showImage($item);
			// change current
			current	= $item.index();
		}
	});
	
	// set elastislide's current to current
	$esCarousel.elastislide( 'setCurrent', current );
	
},

Read more about Elastislide’s options here: Elastislide – A Responsive jQuery Carousel Plugin.

Our next function will take care of the viewing modes and what happens when we switch the views:

_addViewModes	= function() {
	
	// top right buttons: hide / show carousel
	
	var $viewfull	= $('<a href="#" class="rg-view-full"></a>'),
		$viewthumbs	= $('<a href="#" class="rg-view-thumbs rg-view-selected"></a>');
	
	$rgGallery.prepend( $('<div class="rg-view"/>').append( $viewfull ).append( $viewthumbs ) );
	
	$viewfull.bind('click.rgGallery', function( event ) {
		$esCarousel.elastislide( 'destroy' ).hide();
		$viewfull.addClass('rg-view-selected');
		$viewthumbs.removeClass('rg-view-selected');
		mode	= 'fullview';
		return false;
	});
	
	$viewthumbs.bind('click.rgGallery', function( event ) {
		_initCarousel();
		$viewthumbs.addClass('rg-view-selected');
		$viewfull.removeClass('rg-view-selected');
		mode	= 'carousel';
		return false;
	});
	
},

The _addImageWrapper function adds the structure for the large image and the navigation buttons if the number of total items is greater than one.
It also initializes the navigation events. Using the jQuery Touchwipe Plugin, we’ll add support for the wipe gesture and also for keyboard navigation:

_addImageWrapper= function() {
	
	$('#img-wrapper-tmpl').tmpl( {itemsCount : itemsCount} ).appendTo( $rgGallery );
	
	if( itemsCount > 1 ) {
		// addNavigation
		var $navPrev		= $rgGallery.find('a.rg-image-nav-prev'),
			$navNext		= $rgGallery.find('a.rg-image-nav-next'),
			$imgWrapper		= $rgGallery.find('div.rg-image');
			
		$navPrev.bind('click.rgGallery', function( event ) {
			_navigate( 'left' );
			return false;
		});	
		
		$navNext.bind('click.rgGallery', function( event ) {
			_navigate( 'right' );
			return false;
		});
	
		// add touchwipe events on the large image wrapper
		$imgWrapper.touchwipe({
			wipeLeft			: function() {
				_navigate( 'right' );
			},
			wipeRight			: function() {
				_navigate( 'left' );
			},
			preventDefaultEvents: false
		});
	
		$(document).bind('keyup.rgGallery', function( event ) {
			if (event.keyCode == 39)
				_navigate( 'right' );
			else if (event.keyCode == 37)
				_navigate( 'left' );	
		});
		
	}
	
},

The navigation through the large images is controlled by the following function:

_navigate		= function( dir ) {
		
	if( anim ) return false;
	anim	= true;
	
	if( dir === 'right' ) {
		if( current + 1 >= itemsCount )
			current = 0;
		else
			++current;
	}
	else if( dir === 'left' ) {
		if( current - 1 < 0 )
			current = itemsCount - 1;
		else
			--current;
	}
	
	_showImage( $items.eq( current ) );
	
},

Depening in which direction we are moving, we are setting the current element to one less or one more.

And finally we’ll define the _showImage function that will add the large image and its caption:

_showImage		= function( $item ) {
	
	// shows the large image that is associated to the $item
	
	var $loader	= $rgGallery.find('div.rg-loading').show();
	
	$items.removeClass('selected');
	$item.addClass('selected');
		 
	var $thumb		= $item.find('img'),
		largesrc	= $thumb.data('large'),
		title		= $thumb.data('description');
	
	$('<img/>').load( function() {
		
		$rgGallery.find('div.rg-image').empty().append('<img src="' + largesrc + '"/>');
		
		if( title )
			$rgGallery.find('div.rg-caption').show().children('p').empty().text( title );
		
		$loader.hide();
		
		if( mode === 'carousel' ) {
			$esCarousel.elastislide( 'reload' );
			$esCarousel.elastislide( 'setCurrent', current );
		}
		
		anim	= false;
		
	}).attr( 'src', largesrc );
	
};

return { init : init };

And that’s all folks! I hope you enjoyed this gallery 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 175

Comments are closed.
  1. I wish i can find a woman like you Mary Lou. My life would be complete with a person who uses css,js and html like poetry 😛

    Thanks for god-sent tutorial!

  2. Very Nice!

    Q:
    What do i have to change, so the page will open with the other viewing option (without the thumb carousel) ?

  3. Mary plz help me!

    I want to start learning JQUERY, but I don’t know where I should start and what book I should read as a beginner.
    (I just know html and css)

  4. Hi ML, I have 2 questions, how come your tutorial look so much better than your site :p? and my second one is how do you find solutions for your problem? is there any step by step for it, I’m starting to learn jquery and I’m having so much trouble doing the effect I want to do. If there is any books or tips, everything is welcome. tx

  5. Looks nice and seems to work good. I’m still looking for a way to have a adaptive/responsive image gallery that somehow requests smaller image sizes when on smaller viewport widths.

    Because most galleries are written in html, there is no easy way around this. I thought about doing slideshow using divs with background images but that wouldn’t probably be very semantic i guess…. still fishing.

    Thanks fo the article.
    Perhaps you could reply to some of the above comments from your kind community sometime.

    • Hi PhilD, thank you for your feedback! I am trying to reply to comments but it’s sometimes hard with ~100 a day 🙂 Most of the time I am working on new tutorials, so I am very sorry for not being able to comment more, but I’ll keep trying 🙂

      Do you know about http://adaptive-images.com/ ? I think it offers a good solution to what you need. Thanks again for your feedback, cheers, ML

  6. On one of your other tutorials you used the Jquery Deferred Object.

    Thank you for demonstrating it, I had no idea it existed and was able to use it for a project successfully because of your examples.

  7. Great plugin! works perfectly.

    Can you add a code snippet showing how I can switch view modes with javascript?

  8. Great one!
    I’ll use it for my boyfriends portfolio I think 🙂

    But I have one question and it would be awesome if you’d answer it.

    I changed the mode here to “fullview” but nothing happens. It starts with the thumbs even if I set the mode from “carousel” to “fullview” 🙁

    var current = 0,

    // mode : carousel || fullview
    mode = ‘carousel’,

    // control if one image is being loaded anim = false,

    init = function() {

    • @Sina thanks!
      I fixed it! You can either download the zip file again or just copy the contents of the js file here
      @Pushpinder Bagga the bug you mentioned is also fixed now. Thanks for letting me know!
      Cheers

  9. Yeeeah, now it works! Perfect! Thank you!!!

    I’m sure it’s written somewhere on this wonderful website but can you tell me quickly if I have to give credit or something like that if I use the script in my project?

  10. Truly beautiful, well done! 🙂

    Just one question, as I am not entirely familiar with jQuery yet… Say I want to map the rg-image or the rg-image img objects… How do I do that? I gave it a (not successful) try via the .css but I kind of have the feeling I should be messing with the gallery.js file instead… If anyone has the solution to this, I would be greatful!

    Once again, great job! Keep it coming! 🙂

  11. In the css: .rg-image img{}.
    In the js I think this would also work: $(‘#rg-gallery’).find(‘div.rg-image > img’)

  12. Hi! I just love this gallery! Im already using it! But I have a little question, why cant I use 2 galleries in the same page? Should I change some lines in the code? Thanks!

  13. Thank you ML for the works! I think it’s great.
    So, i use it to make a website for ipad.
    Put it in html table tag.
    How can i change the width of the container? I added code ‘width:560px’ in es-carousel-wrapper of elastislide.css.
    It works. But, thumbnail images are not show up sometimes. But, in IE8, It works very well.

    is this problem of safari, or my added css code?

    Help me. Thank you.

  14. Awesome tutorial using this on one of my clients websites currently i was wondering if their is anyway to select an image on page load? for example on page load i want image 5 to load.

  15. I can’t for the life of me figure out how to make italic text in the captions. I’ve tried

    TEXT

    <em>TEXT </em>

    LOVE the script!!! Just hope there is a work around on this… I bet I am missing something basic?

    • Hi Jeffrey,

      did you figure out, how to solve the problem. I’ve got the same and I’d really like to change it.

      Thanks and cheers!
      Skritz

  16. Very nice script, and love the swipe-effect on my iPad !
    However in a desktop-browser, when the content is placed down the page where I have crolled to, when I click a thumbnail in the carousel, the pages jumps back up… can this be disabled ?
    Tnx !!

  17. Very Nice! Thanx you Mary Lou.
    …and if you want to upload photos via xml or categorize?

    Ciao

  18. One of the best galleries I’ve seen! Thanx a lot for this tutorial!

    Question: How can i change order of content blocks? (I want to place carousel below large image and caption.) Trying everything I can imagine, but without success. 🙂

  19. If you want to place the thumbnail carousel below the big images: On line 142 of gallery.js Change “appendTo” to “prependTo”.

    How can I enable AUTOPLAY?

  20. how can i have two galleries in the same page?

    Coping and pasting the same code i can’t do it

  21. I have images of various dimensions. And I moved the thumbnail carousel under the big images.
    How can I have dynamic margins above and below the large image so that the carousel doesn’t move up and down? Javascript? Who can help me insert the right code?

  22. Hakan, to put slider under large image i changed

    .appendTo( $rgGallery );

    .insertBefore( $rgThumbs );

    and added variable
    $rgThumbs = $rgGallery.find(‘div.rg-thumbs’),

    thats worked for me

  23. How can I change the size of the thumbs?
    Which Jquery version is the minimum required?
    Regards, Isabell

  24. Now, if one would add album support, fancy fade/slide effects to main image container and SlideShowPro Director integration and optional skins I’d say total world domination would be next 😉

  25. Following the ResponsiveGallery demo (which works beautifully and is easy to understand and customize, thank you!), I am attempting to dynamically load more slides into the elastislide via the technique outlined in this (http://tympanus.net/codrops/2011/09/12/elastislide-responsive-carousel/) post, and while with a little tweaking they add to the carousel nicely, the click function isn’t bound to them… it seems like the culprit could be the onClick method used in the elastislide plugin, but I am unsure because the tutorial I linked above seemed to imply that it could handle dynamically inserted content.

    a pointer in the right direction would be greatly appreciated.

    best, Owen

    • @Owen thank you!
      OK, now it works 🙂 I updated the zip file. I added the function “addItems” in gallery.js. in the end of the script you will find an example (commented). I have also updated the jQuery version. Let us know if it works. Best, ML

  26. Hi Mary..Thank’s for this nice sample…it’s possible to add hyperlink in detail image, so when we click on it will open to other page..

    Best Regards

    Haical