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!

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 class="es-carousel">
						<a href="#">
							<img src="images/thumbs/1.jpg" data-large="images/1.jpg" alt="image01" data-description="Some description" />
		<!-- 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 class="rg-image"></div>
		<div class="rg-loading"></div>
		<div class="rg-caption-wrapper">
			<div class="rg-caption" style="display:none;">

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.


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:

	padding:20px 30px;
	background:transparent url(../images/black.png) repeat top left;
	-moz-border-radius: 10px;
	-webkit-border-radius: 10px;
	border-radius: 10px;

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:


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{

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

.rg-image-nav a{
	background:#000 url(../images/nav.png) no-repeat -20% 50%;
	-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{
	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{

The caption will have the following style:

.rg-caption {
.rg-caption p{
	font-family: 'Trebuchet MS', 'Myriad Pro', Arial, sans-serif;
	padding:0 15px;

Now, let’s style the switch options:

.rg-view a{
	background:#464646 url(../images/views.png) no-repeat top left;
	border:3px solid #464646;
.rg-view a:hover{
.rg-view a.rg-view-full{
	background-position:0px 0px;
.rg-view a.rg-view-selected{
.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:

	background:#000 url(../images/ajax-loader.gif) no-repeat center center;
	margin:-23px 0px 0px -23px;
	-moz-border-radius: 10px;
	-webkit-border-radius: 10px;
	border-radius: 10px;

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(''),
	// 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


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
			// add large image wrapper
			// show first image
			_showImage( $items.eq( current ) );
		// initialize the carousel

We need to call the Elastislide plugin:

_initCarousel	= function() {
		imageW 	: 65,
		onClick	: function( $item ) {
			if( anim ) return false;
			anim	= true;
			// on click show image
			// 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();
		mode	= 'fullview';
		return false;
	$viewthumbs.bind('click.rgGallery', function( event ) {
		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
			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 if( dir === 'left' ) {
		if( current - 1 < 0 )
			current = itemsCount - 1;
	_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();
	var $thumb		= $item.find('img'),
		largesrc	= $'large'),
		title		= $'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 );
		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!

Tagged with:

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 up to date with the latest web design and development news and relevant updates from Codrops.

Feedback 175

Comments are closed.
  1. (My comment didn’t seem to work so here it is again.)

    @Mary Lou – Thanks for the update!

    Works great. Nice implementation of the new jquery handler .on()! I was fooling around with .delegate and it wasn’t working as nicely.

    I’ve attempted to modified the gallery plugin to support multiple instances by wrapping it in “return $(this).each( function () { });” and giving each instance a unique ID, which works for the images/nav, however, the $esCarousel selector is used for every instance and adding, destroying, etc. have a global effect… I tried the same technique of unique IDs but to no avail.

    once again a pointer in the right direction would be greatly appreciated!


  2. Yes, this is very nice.

    But having multiple instanced per page capabilities would be a big plus.

  3. Hi,

    verry useful utility tutorial. I’ve one question how to add sound and an automatic slideshow on this example?

    Thanks in advance.

    Regards from spain

  4. Hi Mary,

    Great Gallery, can I ask if it is possible to add play/pause button and add some background sound on it?

    Thanks a lot, regards from Spain

  5. I got multiple instances working, the trick is to wrap the gallery plugin in an each statement per my comment above, and then every time the gallery plugin calls the elastislide plugin, you need to set the selector relative to that instance.

    Using a counter to create unique IDs:
    $(this).attr(‘id’, ‘es-gallery-‘ + g);
    $(this).find(‘’).attr(‘id’, ‘es-carousel-‘ + g);

    Then whenever init the carousel or modifying it, selecting the right one with:
    var $esCarousel = $esGallery.find(‘.es-carousel-wrapper’).attr(‘id’);
    $(‘#’ + $esCarousel).elastislide( ‘setCurrent’, current );

    Maybe there is a better way but this method works.

  6. Hi,

    Thank you Mary Lou, this gallery is great.

    I have a question:
    Vasily mentioned how to put the slider under the large image, but being a newbie to jQuery, I have not be able to understand how to do it.

    Can you help?

    Vasily wrote:


    Many thanks.

    The website I’m working on is here:
    I’ve turned the gallery into a white gallery 🙂

    best regards from New Zealand!

    • @Sambodhi thanks!
      You just need to replace “appendTo” with “prependTo” in gallery.js. Hope it helps! Cheers, ML

  7. Hey Mary Lou,

    thank you so much for all these wonderful tutorials of yours. You make webdesign a little more interesting with every post.

    I adapted this image slider for my own portfolio and figured everything out besides one little thing. As you can see ( I have placed the carousel on the bottom. Everytime you switch to a different image you can now see the carousel flashing below the main view. Do you have any idea how to the rid of the flashing?

    Best wishes from Denmark

    Hey Jessica,

    you can add more images basically the same way you would delete them. Simply copy everything between and , change the paths (src and data-large), the alt and data-description and you should be good to go:)

    • @Severin Thanks! Sorry but I am not seeing it. With which browser are you seeing the page? I have tested with the latest Chrome & Firefox and it seems fine. Cheers, ML

  8. Hey Mary,

    the problems appears only in the lastest version of chrome for OSX. It works fine in Firefox, Safari and Opera. I tried to reset Chrome and clear its cache, but it doesn’t seem to help. But it is indeed strange that it only appears to happen in Chrome.

    Thank you for the quick reply 🙂

    • @Severin I honestly don’t know what the problem can be. I will try to take a look though. Anybody else with the same problem?? Thanks!
      Cheers, ML.

  9. Hi, great plugin 🙂

    But, it stops being responsive when it is inside a container..? Is that by design?

  10. Hi, Mary. This is fantastic gallery. Can you help me, i have thumbnails with rectangular dimensions, how can i add padding between thumbnails in a slider. I can’t do it

    Your sincerely.

    • Go to eslastislide.css
      and add this to the list in….. .es-carousel ul li { …..

      padding:10px 15px;

    • I found it! If you go to gallery.js and on line 91 there is the variable “imageW :” change that number to the new width of your thumbnails in px. The height will auto adjust so make sure thery’re all the same new height.

  11. Hi,
    I got a problem getting your awesome Imageslider work in thickbox (a jquery based lightbox). The image slider just don’t work anymore if I’m opening the gallery in a thickbox.
    Could you plzzzzzzz help me?

  12. I’m in the process of turning this into a plugin and also adding PrettyPhoto. I’m running into some issues with PrettyPhoto integration. If anyone is interested in taking a look @ChiefAlchemist1

    I’m also not able to get it to automatically switch from image to image. That never worked for me so I don’t think it’s my plugin converting that did it. I’d gladly accept some input in resolving that as well.

    Finally, I’d like to make it multiple instances per page. I saw someone mentioned something about looping, etc but I would think there’s go to be a more elegant way, yes?

    • I just needed that ability myself and figured this out….
      var $thumb = $item.find('img'), largesrc = $'large'), title = $'description'); herf = $'herf'); $('').load( function() { $rgGallery.find('div.rg-image').empty().append('<a href="' + herf + '" rel="nofollow"></a>');

  13. great responsive design but still tryng to crack JS code to add a css3 fade transition and an autoplay option.
    hints anyone ?

  14. Great Plugin! I got it working but I’m not technical enough to make the multiple instances fix mentioned above. I also was having problems with the latest Google Chrome (chrome’s been buggy recently). So after a number of hours of work, I’m going with another solution, but I’m keeping this in my back pocket for sure! I love it so much and can’t wait to use it.

  15. Thanks for this great plugin and the well written explanations.
    The only thing that doesn’t work for me is running it on internet explorer (ie9).
    Desperately need a solution.

  16. @Severin do you have adblock extension for chrome? try disabling it – I had a similar issue to the one you describe and it turned out to be the ad-block extension doing something strange when the images were swapped.

  17. Great plugin/tutorial, that’s exactly what I was looking for…
    You’re awesome! Thank you for sharing this great work!

  18. The script is awesome.. gr8 innovative idea. Could anyone help me with the script which i want some changes to prior one.

    1. Thumbnail at bottom + if possible with mirror effects ( 30% ) to thumbnail images.
    2. Thumbnail navigation using mouse scroll when the cursor is on the thambnails,
    3. Auto pop up when mouse hover.

    • To Move the thumbs below the large image you need to make few changes in gallery.js and your HTML page.

      First add a line no 2 in HTML Page (Below is the modified HTML line no 2, Just add a with id “rg-gallery1”)


      Now make changes in gallery.js file

      Create a variable as below line no 1

      1. var $rgGallery1 = $(‘#rg-gallery1’);
      // gallery container
      2. var $rgGallery = $(‘#rg-gallery’),

      Now final change in gallery.js

      find the line —> // also initializes the navigation events

      and replace “$rgGallery” with “$rgGallery1”

      $(‘#img-wrapper-tmpl’).tmpl( {itemsCount : itemsCount} ).appendTo( $rgGallery1 );


    • elastslide.css :
      .es-carousel ul li a img{
      height: 200px;
      width: 200px;


      imageW : 200,


  19. Thank you so much for this great plugin. I have it working just fine, but I am looking to add a full screen feature to the slideshow, adding it to the icons that toggle the carousel on/off. Turns out the HTML is generated by the JS file “gallery.js”. I can work with js that needs to be changed, but I can only modify existing JS code, not write it (yet!).

    This is what I came up with, but it is nowhere near complete. Even a link to a tutorial on what is happening here and how to add to it would be most welcome.

    // top right buttons: hide / show carousel var $viewfull = $('<a href="#" rel="nofollow"></a>'), $viewthumbs = $('<a href="#" rel="nofollow"></a>'); $viewfullscreen = $('<a href="#" rel="nofollow"></a>') $rgGallery.prepend( $('').append( $viewfull ).append( $viewthumbs ).append($viewfullscreen ); $viewfull.on('click.rgGallery', function( event ) { if( mode === 'carousel' ) $esCarousel.elastislide( 'destroy' ); $esCarousel.hide(); $viewfull.addClass('rg-view-selected'); $viewthumbs.removeClass('rg-view-selected'); mode = 'fullview'; return false; }); $viewthumbs.on('click.rgGallery', function( event ) { _initCarousel(); $viewthumbs.addClass('rg-view-selected'); $viewfull.removeClass('rg-view-selected'); mode = 'carousel'; return false; }); if( mode === 'fullview' ) $viewfull.trigger('click'); }, _addImageWrapper= function() {

  20. Any tips on moving the thumbnails below the main image. Nash’s post is missing something I think.

    Thank you.

  21. Hei!

    great work.
    I included in my WordPress as a article slidehow and it works really fine.
    But I have a problem if I want to inclode bold text, paragraphs or something else. It is shown as my text for example. How can I fix this problem?

    Would be nice to hear some ideas.

  22. Again, any tips on moving the thumbnails below the main image please?
    Nash’s post is missing something I think.

    Thank you.

    • change this line
      $('#img-wrapper-tmpl').tmpl( {itemsCount : itemsCount} ).appendTo( $rgGallery );


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

  23. Great Carousel!

    A question: How do I fade the large images? Now it is a little bit rough when changing.


  24. Hi

    Great plugin. Need to get thumbs below large image, also struggling with instructions already posted, any more help here would be much appreciated.


  25. BUMP

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

    to get thumbs below large image – that was easy!

  26. Awesome work, really a great gallery solution.

    Is there a way for the gallery to display images prior to waiting for the entire group of images to load? Basically skipping the pause occurring while waiting for preloading the images. I have several large images that I want to display and it takes a few seconds for them all to load, hence the pause in showing the first image of the slideshow.

    Thanks for your time!

  27. Hi Mary Lou

    This really is great – an excellent addition to any responsive website. Is it possible to add a class to the prev / next icons so they can be a faded out version of the graphics if there is nothing to scroll to view?


  28. Great job with this!! I’m going to use the thumbnail part only. I’m trying to put different sized images and can’t get them align vertically. Any tips? The relative positions and margins are driving me crazy. Thanks in advanced!!

  29. I am searching for a way to get an effect as with : when using the mouseover on next or prev to get a scrolling until the strip finishs. I tried to discover how I could integrate that function but my knowledge on jquery is limited…

    Is this an interesting enhancement?

  30. One thing I don’t get is when you included the img in the jquery template. I get you want to use the template to see if you need to display the navigation, but you only need to do this once?

    If the actual img was not included in the template you wouldn’t have to keep removing it re-appending every time a new image is displayed. It seems all you actually need to do is change the image source once the image is loaded?

    BTW. great example despite my criticism!

  31. Hi all!

    Love this plugin. Question: Let’s say I only have 5 thumbnails, how do I get them centered on the page?

  32. Any tips on how to make the thumbnails bigger…. I can make them wider (imageW) but that doesn’t seem to make the whole thumb proportinally bigger? It can be done when using elastislide on it’s own but I can’t make it work in this context.

    • Just try adding it to the img css style. Works for me!
      p.s. You also need to change the imageW value.

  33. Love the tutorial, many thanks for your hard work. I have a question/issue: I added this to my custom WordPress theme and it seems to be working except it is not loading the first image on page load. When you click on the first image thumbnail, it then loads. Initially it just shows the loading GIF and never replaces it with the large image. BTW, I’m creating the list of image thumbs and links dynamically using PHP and SSP Director.
    I’m a novice at javascript so I’m not sure what might be going on. What might I do to troubleshoot this?

    • Answered my own question… Because I’m in WordPress, I had to change the jQuery to no-conflict mode (changing the “$” to “jQuery”). I had missed a couple of instances of the dollar sign.
      But I am wondering what a few other people are asking: is there a way to do an autoplay slideshow? Also, how about transition effects like fade or slide?
      Again, great work! Thanks!

  34. I would love to use this for a WordPress theme I am developing but I need to show videos. Anyone get videos working with this?

  35. Can someone help me here? I want to add pause play functionality to that and I dont know how.

  36. i’ve added your gallery to my webpage and something i think in my style sheet is forcing the gallery to show the large thumb stacked twice on top of itself. Has anyone else experienced this before?

  37. oh yeah, and actually it only does this in IE7 and IE8. Looks great in everything else.

  38. Good job; works great. One small suggestion:
    Instead of of adding a data-larger attribute on the thumb, it would be better to use the href of the anchor tag. In that way all content is accessible without javascript.