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. Fantastic script! Looks great in the site I’m designing! I need to use more than one in the same page though. What’s the best way of implementing this? Thanks!

    • You can put multiple galleries on the same page by editing the gallery.js so that it is a normal function instead of a self-executing anonymous function which you can pass the unique id of each gallery that can be used where $rgGallery is defined. Here is the edited file gallery.js

  2. I have the same problem… cant add multiple galleries on one page. I have searched through the rest of the comments and it doesnt seem to be an answer.

    Any help?

  3. Hello loving your image slider – thank you so much for sharing.

    I’m using your image slider in one of my projects and I have a picky client that spotted that when clicking the first and last thumbnail the nav button the was hidden shows up again. Any ideas on how to fix this?

    Many thanks.

    • Thanks for coming back so quickly – it’s the little arrows that control the thumbnails they seem to go transparent when there are no more thumbnails left or right which is great, but oddly on the first and last thumbnails the buttons show up again. I’m still quite new at all this an I cant seem to find a solution. I have probably done something in the css or something to cause this issue. Here’s a link to the page http://www.livewebtesting.co.uk/zipbar/test/gallery.php any suggestions would be most appreciated.

  4. This plug-in is great. I have two questions…I’m realtively new to this so forgive me if they are silly or not possible.
    1) Is there a way to make the plug-in default open to full screen instead of thumbnail view?
    2) Is there a way to have the thumbnail carousel and upper right view buttons at the bottom instead of the top? Or just the thumbnails at the bottom?

    Many thanks!

    • At the line 142 of gallery.js change
      appendTo( $rgGallery )
      to
      prependTo( $rgGallery )/
      This way image wrapper will be inserted before the thumbs.

  5. I figured out how to set the initial load as fullscreen, so please disregard that from my previous comment. I have one other question/observation:

    For some reason in Firefox if the image height is taller than 600px it loads initially with a large stripe of the background color and pattern across it, just below where the navigation arrows are (I can post a link to an example or photo of it if you need). The stripe appears on every image until you scroll through them all once, then disappears. It will appear again if you refresh the browser. It doesn’t seem to happen at all in Safari as far as I can tell. Any thoughts on what this is or how to get rid of this? Anyone else have this trouble?

    Thanks.

  6. I have the same problem as jenna.
    A bar across the whole image. (for each image)
    WinXP
    Firefox 11

    A little tip would be nice.

    Many thanks!

  7. Hi, great gallery, Mary or anybody knows how to have a different width for the thumbs?, for most of the thumbs I have imageW : 143, but I need a different width for the first element, I have try with:

    .es-carousel ul li:first-child{
    width:73px !important;
    }

    But this don’t really work at all.

    • resolved the rounded corner issue but still can’t figure out how to hide the toogle icons

  8. *** RESOLVED ***
    LINE 80 – 92 in style.css

    I added display:none;

    .rg-view a.rg-view-full{
    background-position:0px 0px;
    display:none;
    }
    .rg-view a.rg-view-selected{
    background-color:#6f6f6f;
    border-color:#6f6f6f;
    display:none;
    }
    .rg-view a.rg-view-thumbs{
    background-position:0px -16px;
    display:none;
    }

    Thanks for the code and scripts…!

  9. i dont know why, but i have strange problems with max-height for .rg-image img { – its kinda ignored in FF,O, IE …
    any solutions in mind ?

    btw; great stuff and excelent plugin ;]

  10. Great plugin.
    How to integrated it with wordpress ?
    is there any easy way for it ?

    Thanks

  11. Great tutorial, thanks Mary!

    I managed to get fading animation to work.

    In gallery.js in .load function at line 220 you need to add:

    …$(”).load( function() {

    $rgGallery.find(‘div.rg-image’).fadeOut(500, function() { //Fade-Out

    $rgGallery.find(‘div.rg-image’).empty( ).append(”) //Image change

    }).fadeIn(500); //Fade-In

    if( title )…

    When you do that the images will animate on thumbnail click but you may have a problem with the website scrolling to the top (I had that problem). I solved it by adding percentage heights in style.css:

    .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;
    height:95%; //Here
    max-height: 78%; //Here
    }
    .rg-image{
    max-height: 100%; //Here
    height:95%; //And here

    I’m not sure if it ruins the responsivenes of the site or not 🙂 Hope it helps.

    • in gallery.js:
      $rgGallery.find(‘div.rg-image’).fadeOut(100, function() { //Fade-Out
      $rgGallery.find(‘div.rg-image’).empty().append(”);
      }).fadeIn(500); //Fade-In

      in style.css:
      .rg-image{
      position:relative;
      text-align:center;
      line-height:0px;
      min-height:480px; /*Your min height image*/
      }

  12. Has anyone been able to get this gallery to work within ASP.net MVC? I can get the thumbnail bar to work but the way that large image loads within the script tags in the header I can’t get hat piece to work.

  13. Everyone asking for the thumbnails below the image, it was answered several times in the comments.

    “change this line
    $(‘#img-wrapper-tmpl’).tmpl( {itemsCount : itemsCount} ).appendTo( $rgGallery );

    to

    $(‘#img-wrapper-tmpl’).tmpl( {itemsCount : itemsCount} ).prependTo( $rgGallery );”

    Now, does anyone know how we could add autoplay to the slideshow?

    • I think the there are some compatibility issues with this some versions of IE9. I’m not having large image issue in browsers like FF, Chome, or Safari.

  14. Hi Mary , love your plugin . But I can’t get any of the popup plugins to work with your plugin , as I want the Big image to be clickable and then popup a larger size . I’ve tried Facebox , lightbox , thickbox , fancybox etc and none of them work . Clicking the image will just redirect me to a new page with the image . Any help here ?

    • Hi,

      Hi Eric,

      I’m trying to do exactly the same thing but don’t understand what you mean by “taking codes outside from the template to normal div”.

      Could you please put in a little bit of sample code or a link?

      Thanks : )

      James

  15. Hi all,

    I’ve seen many time people ask if it’s possible to have an automatic slideshow.
    I wanted the same thing and I find a way to do it.
    In the file gallery.js, in the function _initCarousel add these lines after $esCarousel.elastislide( ‘setCurrent’, current ); (~ line 103) :

    window.setInterval(function(){
    _navigate( ‘right’ );
    }, 5000);

    You just have to change 5000 to the value you want.

  16. Hi all,

    Here how to have a basic fancy box integration (I just tested rapidly because I don’t need it but I’ve seen many people ask how to have a popup when large image is clicked).

    First you have to include the js and the css file of fancybox in the file where you have the carousel.

    After in the file gallery .js, replace the line 222 : $rgGallery.find('div.rg-image').empty().append(''); by $rgGallery.find('div.rg-image').empty().append('<a href="'+largesrc+'" rel="nofollow"></a>');

    In the file where you have the carousel, had this in the end of the body :

    $(document).ready(function() {
    $(“.fancybox”).fancybox();
    });

    That’s all folks !

  17. Hi all,

    Here how to have a basic fancy box integration (I just tested rapidly because I don’t need it but I’ve seen many people ask how to have a popup when large image is clicked).

    First you have to include the js and the css file of fancybox in the file where you have the carousel.

    After in the file gallery .js, replace the line 222 : $rgGallery.find(‘div.rg-image’).empty().append(‘img src=”‘ + largesrc + ‘”‘); by $rgGallery.find(‘div.rg-image’).empty().append(‘a class=’fancybox’ href=”‘+largesrc+'” img src=”‘ + largesrc + ‘” a’);

    In this line, don’t forget to add the ” for the img and link tags.
    I can’t add there here because if I do, the tag aren’t show.

    In the file where you have the carousel, had this in the end of the body :

    $(document).ready(function() {
    $(“.fancybox”).fancybox();
    });

    That’s all folks !

  18. I’m sorry for the duplicate post but there was a problem in the lines of code I posted : in the append there is a part of code that I wrote in my previous message but wich doesn’t appear (I think it’s because the img and link tags are perform).
    So I post a version without the stripe and precise to add them.
    You can delete this post and the duplicate useless post.

    Regards
    James

  19. How is it possible to integrate fancybox in main demo?
    I want to do like that when click on main image then whole slider open in fancybox & when fancybox close then slider as it is display & also want like that when next/prev click then main image sliding with slide effect

    How is it possible?

    It’s urgent for me.

  20. I like your gallery very much, but am finding that I have to keep two copies of (two of) the files in the images directory – ajax-loader.gif and black.png

    Maybe I’m not doing it right, but FYI in case it is an actual problem.

    thanks!

    —— find all on ‘ajax-loader’ ———-
    style.css:
    background:#000 url(../images/ajax-loader.gif) no-repeat center center;
    gallery.js
    $items.add(”).imagesLoaded( function() {

    • I think I answered my own question. I changed the code from
      “””” http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js“”””
      to
      “”””http://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js””””
      It seemed the conflict was the to different versions of the jquery library. A newer verson was needed for both the website and the gallery to work. Sound plausible? I’m new at this…..

  21. Mary Lou, Is this gallery SEO friendly? My client seems to think that having each image link to a separate page would be better for Google to crawl, index and rank. How can I improve the SEO for this gallery or do I even need to?

  22. I’ve implemented this and think it is swell. However, has anyone figured a way to include videos in the gallery? I saw this question asked in the comments years ago, but I didn’t find any responses. If I could add videos, this would be sheer perfection.

  23. @Nick Fredricks
    if you can share the complete code how to add mulitple gallery into one page? newbie on the code. thank you in advance!