How to Make an Elegant Sliding Image Gallery with jQuery

Learn how to build a nice-looking sliding photo gallery using JavaScript and jQuery. Full code download included.

How to Make an Elegant Sliding Image Gallery with jQuery

There are a lot of jQuery image galleries out there, some of which are quite lovely (Galleria springs to mind). However, I've yet to discover a really nice-looking, full-window sliding image gallery that is simple and elegant, and that also shows off the photos to good effect. So I decided to write one!

To see what it looks like, click the View Demo button above. It looks best on WebKit browsers such as Safari and Chrome, but other modern browsers should work well too.

For best results, make your browser full-screen. Google Chrome gives the most immersive experience here — view the demo, then choose View > Enter Full Screen.

The gallery is simple to use. Click the left or right edge of the window to move around the pictures (or use the left and right arrow keys on your keyboard). Hover over the main image to view its caption.

I've also made the gallery work reasonably well on touch devices such as iPhones and iPads by adding touch and swipe events. (I haven't yet tested these on the Android browser so your mileage may vary there. Watch this space!)

So how do you make this gallery? In this tutorial I'll walk you through the process step-by-step, from creating the markup and CSS through to writing the JavaScript code to make the gallery function. If you'd rather just grab the code and get going, click the Download Code button above.

If you like this gallery, please feel free to use the code in your own websites and projects. It's Creative Commons licensed.

Ready? Let's get started!

Step 1: Create the markup

First we need to create a webpage to hold our gallery's images, CSS and JavaScript. Here's the HTML for our gallery page's body element:


  <button id="leftButton" onclick='moveLeft()'>&lt;</button>
  <button id="rightButton" onclick='moveRight()'>&gt;</button>
  <div id="galleryContainer">
    <div id="gallery">
      <img src="slides/BarbedWire.jpg" alt="Barbed Wire" />
      <img src="slides/Feather.jpg" alt="Feather" />
      <img src="slides/DriftStuff.jpg" alt="Drift Stuff" />
      <img src="slides/Driftwood.jpg" alt="Driftwood" />
      <img src="slides/DriftwoodGuy.jpg" alt="Driftwood and Guy" />
      <img src="slides/GrassLight.jpg" alt="Grass and Light" />
      <img src="slides/PebbleAndShells.jpg" alt="Pebble and Shells" />
      <img src="slides/StickSea.jpg" alt="Stick and Sea" />
      <img src="slides/SeaweedGasmask.jpg" alt="Seaweed Gasmask" />
      <img src="slides/Surfers.jpg" alt="Surfers" />
    <div id="caption">Photo Caption</div>
    <div id="loading">Please wait...</div>


The markup contains:

  • 2 button elements for the left & right buttons (leftButton and rightButton).
    We bind each button's click event to a function that slides the gallery 1 image to the left or right (moveLeft() and moveRight() respectively). Each button has an arrow (< or >) for a label. We'll use CSS later to style these buttons as large, transparent buttons that sit on the left and right sides of the gallery.
  • A container for the gallery (galleryContainer).
    The sliding gallery sits inside this div.
  • The gallery itself (gallery).
    This is the div that contains the slide images. We will slide this div left and right to show each photo.
  • The slide images inside the gallery div.
    Each slide is simply an img tag containing the photo's URL, and some alt text which we'll display as a caption when the user hovers over the image.
  • The photo caption container (caption).
    We'll position this just below the current gallery image. It will hold the caption text to display for the current image.
  • The loading message (loading).
    This div contains the "Please wait.." text that will appear if the gallery images are taking a while to preload.

Step 2: Add the CSS

Now we'll add the CSS to our page to style the gallery. Here's the complete CSS:


body {
  margin: 0;
  padding: 0;
  background: #000;
  font-family: "Georgia", serif;

  Container for the gallery:

  Absolutely positioned
  Stretch to fill the whole window width
  Fixed height
  Hide the overflow to prevent horizontal scrollbars

  Vertically centred in the viewport: 

#galleryContainer {
  width: 100%;
  height: 800px;        /* Image height + 200px */
  overflow: hidden;
  position: absolute;
  top: 0;
  bottom: 0;
  margin-top: auto;
  margin-bottom: auto;
  z-index: 1;

  The gallery div that contains all the images

  We'll set the width dynamically in the JavaScript as the images load

#gallery {
  width: 100px;
  height: 700px;        /* Image height + 100px */
  padding: 50px 0 50px 0;
  position: absolute;
  z-index: 1;

  Individual slides within the gallery:

  Float them left so that they're all side by side
  Fixed height (the width will vary as required)
  Add some horizontal margin between the slides
  Add a bottom fading reflection for WebKit browsers

#gallery img {
  float: left;
  height: 600px;
  margin: 0 100px;      /* Adjust the left/right margin to show greater or fewer slides at once */
      left top,
      left bottom,
      color-stop(1, rgba(255, 255, 255, .5)),
      color-stop(.8, rgba(255, 255, 255, 0))

  Left and right buttons:

  Position them on the left and right sides of the gallery
  Stretch them to the height of the gallery
  Hide them by default

#leftButton, #rightButton {
  position: absolute;
  z-index: 2;
  top: -100px;
  bottom: 0;
  padding: 0;
  margin: auto 0;
  width: 15%;
  height: 600px;        /* Image height */
  border: none;
  outline: none;
  color: #fff;
  background: transparent url(images/blank.gif);
  font-size: 100px;
  font-family: "Courier New", courier, fixed;
  opacity: 0;
  filter: alpha(opacity=0);
  -webkit-transition: opacity .5s;
  -moz-transition: opacity .5s;
  -o-transition: opacity .5s;
  transition: opacity .5s;

#leftButton {
  left: 0;

#rightButton {
  right: 0;

/* (Turn off dotted black outline on FF3) */

#leftButton::-moz-focus-inner, #rightButton::-moz-focus-inner {
  border: none;

  Left and right button hover states:
  Fade them in to 50% opacity

#leftButton:hover, #rightButton:hover {
  opacity: .5;
  filter: alpha(opacity=50);
  outline: none;

  Image caption:

  Position just under the centre image
  Hide by default

#caption {
  position: absolute;
  z-index: 2;
  bottom: 90px;
  width: 100%;
  color: #ffc;
  text-align: center;
  font-family: "Georgia", serif;
  font-size: 24px;
  letter-spacing: .1em;
  display: none;

  Loading text:

  Position in the centre of the gallery container
  Hide by default

#loading {
  position: absolute;
  z-index: 1;
  bottom: 50%;
  width: 100%;
  color: #ffc;
  text-align: center;
  font-family: "Georgia", serif;
  font-size: 36px;
  letter-spacing: .1em;
  opacity: 0;
  filter: alpha(opacity=0);


<!-- IE7 positions the buttons incorrectly; compensate -->

<!--[if lt IE 8]>
#leftButton, #rightButton {
  top: 50px;

Let's look at each rule in the CSS:

  1. The page body
    We remove all margin and padding on the body element so that our gallery goes right to the edge of the window. We also give the body a black background and use Georgia for the font.
  2. The gallery container
    #galleryContainer is the div that contains our sliding gallery. We stretch it across the width of the browser window and give it a height of 800 pixels — this should be 200 pixels more than the height of our image slides. We set overflow to hidden so that the slides outside the browser window don't create a horizontal scrollbar, and position the container in the vertical centre of the page.
  3. The gallery
    #gallery is the sliding gallery itself. We give it an initial width of 100px (we'll adjust this later in the JavaScript), and a height of 700px — that is, 100px more than the height of the slide images. We also add some vertical padding to the gallery to position it nicely in the page.
  4. The slide images
    #gallery img selects the individual slide photos in the gallery. We float the images left so that they all sit side by side in a row; set the image height (600px); and add 100px of left and right margin to separate each slide. We also add a reflection effect below each image using the -webkit-box-reflect property (as you'd imagine, this only works in WebKit browsers such as Safari, Mobile Safari, and Chrome). We apply a semitransparent gradient mask to the reflection to fade the reflection towards the bottom.

    You can adjust the margin property to suit. For example, change this value to 0 50px to put the images closer together. Or you could change it to 0 1000px to ensure that only 1 image is ever displayed onscreen at once.

  5. The left and right buttons
    #leftButton & #rightButton are the buttons on either side of the gallery. The user can click these buttons to slide left and right between the images.

    We restyle these button elements quite drastically. We position them on the left and right sides of the page; give them a z-index of 2 so they sit above the gallery; centre them vertically in the page (moving them up 100 pixels to allow for the reflections below the images); and give them the same height as the images (600px).

    We also turn off all borders and outlines that are usually applied to buttons, and give the buttons a white foreground colour and a transparent background. We set a large Courier font on the buttons so that the arrows (< and >) are large and nice-looking.

    Finally, we make the buttons invisible by setting their opacity to zero, and add a :hover state to the buttons to bring up the opacity when the user moves the mouse over them. We use the transition property to fade the buttons in and out slowly over half a second.

    The ::-moz-focus-inner rules ensure that the outlines are properly turned off in Firefox 3. (Firefox 4 only requires outline: none.)

  6. The image caption
    #caption is the div containing the text caption that appears below the current slide image when the user hovers over the image. We position it 90px from the bottom of the gallery container, give it a tan colour, and centre it horizontally. We adjust the font size and letter spacing to make the text attractive, and hide the caption initially using display: none.
  7. The loading text
    #loading is the "Please wait..." text that appears if the slide images are taking a few seconds to preload. We position it in the centre of the window, and style and colour the text in a similar way to the image caption. We set its opacity to zero initially to hide it.

Internet Explorer 7 has a problem with vertically positioning the left and right buttons. We compensate for this by adding a conditional comment for IE7 to adjust the top property on the buttons.

I've also added CSS to style the "info" button and box in the bottom right-hand corner. Since this isn't part of the gallery we won't talk about it here.

Step 3: Include the JavaScript libraries

jQuery logo
<script type="text/javascript" src=""></script>
<script type="text/javascript" src="jquery.jswipe-0.1.2.js"></script>

I've used 2 JavaScript libraries for this gallery:

  • jQuery (hosted on Google's CDN)
  • jQuery Swipe, a jQuery plugin that we can use to detect swipe gestures on mobile devices. We'll use this to let users swipe left and right to move between the gallery images.

jQuery Swipe isn't available on a CDN, so you'll need to download it, then put the jquery.jswipe-0.1.2.js file in the same folder as your gallery page.

Step 4: Set up the configuration options

Our first chunk of JavaScript code creates some configuration settings, making it easy for us to adjust the look and feel of the gallery:

//  --- Begin Config ---
var preloadSlides = 3;                // Number of slides to preload before showing gallery
var loadingMessageDelay = 2000;       // How long to wait before showing loading message (in ms)
var loadingMessageSpeed = 1200;       // Duration of each pulse in/out of the loading message (in ms)
var loadingMessageMinOpacity = 0.4;   // Minimum opacity of the loading message
var loadingMessageMaxOpacity = 1;     // Maximum opacity of the loading message
var captionSpeed = 1200;              // Duration of the caption fade in/out (in ms)
var captionOpacity = 0.5;             // Maximum opacity of the caption when faded in
var swipeXThreshold = 30;             // X-axis minimum threshold for swipe action (in px) 
var swipeYThreshold = 90;             // Y-axis maximum threshold for swipe action (in px) 
var leftKeyCode = 37;                 // Character code for "move left" key (default: left arrow)
var rightKeyCode = 39;                // Character code for "move right" key (default: right arrow)
var currentSlideOpacity = 1.0;        // Opacity of the current (centre) slide
var backgroundSlideOpacity = 0.5;     // Opacity of the slides either side of the current slide
//  --- End Config ---

The comments to the right of the settings should be fairly self-explanatory. These settings control things like the number of slide images that need to load before we first start displaying the gallery; delays and animation speeds for the loading message and captions; various opacity settings; which keys should be used for moving left and right; and so on.

You probably won't see the "Please wait..." loading message if you're on a fairly fast connection. To see it, try setting preloadSlides to 10 and reducing loadingMessageDelay to 0. Then upload everything to your server.

Step 5: Set up some global variables

We use a few global variables to track the state of the gallery:

var slideHorizMargin = 0;             // Number of pixels either side of each slide
var buttonHeight = 0;                 // Temporary store for the button heights
var currentSlide = 0;                 // The slide that the user is currently viewing
var totalSlides = 0;                  // Total number of slides in the gallery
var slides = new Array();             // Holds jQuery objects representing each slide image
var slideWidths = new Array();        // Holds the widths (in pixels) of each slide
var slideLoaded = new Array();        // True if the given slide image has loaded
var loading = true;                   // True if we're still preloading images prior to displaying the gallery

Again, the comments should be fairly self-explanatory. We set up various variables to track things like the margin between the slides (so we can calculate the gallery offset correctly), the currently-displayed slide, and the total number of slides in the gallery. We also use 3 arrays to hold the slide objects themselves, along with information about each slide image's width and loaded state.

Step 6: Create the init() function

The init() function is called when the DOM is ready:

$( init );

init() sets up the gallery so that it's ready to use. Here's the function in full:

// Set up the gallery once the document is ready

function init() {

  // Grab the horizontal margin between slides for later calculations
  slideHorizMargin = parseInt( $('#gallery img').css('margin-left') );

  // Hide the gallery and left/right buttons
  $('#gallery').fadeTo( 0, 0 );
  buttonHeight = $('#leftButton').css('height');

  // If the requried number of slides haven’t loaded after ’loadingMessageDelay’ ms,
  // start fading in the loading message

  $('#loading').delay( loadingMessageDelay );

  // Bind the handleSlideLoad() handler function to each slide’s load event
  $('#gallery img').load( handleSlideLoad );

  // For each of the slide images:
  // 1. Hide the slide
  // 2. Record its serial number (0 = the first slide)
  // 3. Store it in the slides array
  // 4. Trigger the load event if the image is already cached (for IE and Opera)

  $('#gallery img').each( function() {
    $(this).data( 'slideNum', totalSlides );
    slides[totalSlides++] = $(this);
    if ( this.complete ) $(this).trigger("load");
    $(this).attr( 'src', $(this).attr('src') );
  } );

  // Re-centre the current slide whenever the user resizes the browser
  $(window).resize( centreCurrentSlide ); 

  // Set the initial show/hide states of the left and right buttons

  // Set the caption text to the alt text of the first slide
  $('#caption').html( slides[currentSlide].attr('alt') );

  // Bind the moveRight() and moveLeft() functions to
  // the swipeLeft and swipeRight events respectively.
  // (IE chokes on the swipe plugin, so skip this code on IE)

  if ( !$.browser.msie ) {

    $('#gallery').swipe( {
         swipeLeft: moveRight,
         swipeRight: moveLeft,
         threshold: { x:swipeXThreshold, y:swipeYThreshold }
    } );

  // Bind the moveleft() and moveRight() functions to the
  // "move left" and "move right" keys on the keyboard

  $(document).keydown( function(event) {
    if ( event.which == leftKeyCode ) moveLeft();
    if ( event.which == rightKeyCode ) moveRight();
  } );

  // Show/hide the tutorial info message when touched (for touch devices)
  $('#info').bind( 'touchstart', function() { $(this).toggleClass('hover'); } );

Let's look at each chunk of code in this function:

  1. Grab the horizontal margin between slides for later calculations.
    We grab the margin-left CSS property of the gallery images and store this value in slideHorizMargin. We'll use this value later when calculating the image offsets relative to the left hand edge of the gallery.
  2. Hide the gallery and left/right buttons
    We want the gallery and buttons to be hidden while the first few images preload. Once enough images have loaded, we'll fade in the gallery and buttons. So we use the jQuery fadeTo() method to reduce the gallery's opacity to zero, and also move it way above the top of the browser viewport (this stops IE stuttering with the loading message). We also reduce the height of the buttons to zero, since simply hiding them doesn't prevent them from appearing when hovered over. (We store the button heights in the buttonHeight variable so we can restore them later.)

    I've used fadeTo( 0, 0 ) rather than css('opacity', 0) because the latter doesn't hide the gallery in IE.

  3. Fade in the loading message.
    We call the fadeInLoadingMessage() function to set up a pulsing animation for the "Please wait..." loading message. (We'll explain this function later.) We use the jQuery delay() method to add a delay to the loading message animation (2 seconds by default). This gives a chance for the images to preload. If they haven't preloaded after this time then the animation will start.
  4. Bind the handleSlideLoad() handler function to each slide's load event.
    Next we use the jQuery load() method to bind a function called handleSlideLoad() to each slide image's load event. This means that, when an image has fully loaded, handleSlideLoad() is called for that image. We'll explain handleSlideLoad() in a moment.
  5. Process the slide images.
    The next chunk of code uses the jQuery each() method to loop through each image in the gallery. For each image, it first hides it so that it can be faded in nicely once it's loaded. Then it uses jQuery's data() method to attach the slide's serial number to the slide, storing it in the slideNum key. This makes it easier for us to track the slides later. We also add the slide's jQuery object to the slides array so we can quickly access it later.

    Then, we trigger the slide's load event if its complete property is true. This ensures that each slide's load event is fired, even if the slide image was previously cached. (Some browsers, such as IE, don't fire load events for cached images.)

    Update 18 March: I added the last line in this code block — $(this).attr( 'src', $(this).attr('src') ); — because IE9 seems to have additional problems firing load events on images. (Not sure if this is a known bug, since IE9's only just launched.) This hack, which sets the image's src property to itself, seems to work around this problem.

  6. Re-centre the current slide whenever the user resizes the browser.
    When the user moves from one slide to the next, our JavaScript code centres the current slide horizontally in the browser window. But what if the user resizes their browser window? When that happens, we need to reposition our gallery so that the current slide is still centred horizontally. To do this, we attach the centreCurrentSlide() function to the window's resize event. (We'll get to centreCurrentSlide() later in the tutorial.)
  7. Set the initial show/hide states of the left and right buttons.
    Next we call setButtonStates(), which shows or hides the left and right buttons based on the image currently being viewed. In this case, it will hide the left button since we're showing the first slide. We'll cover this function later.
  8. Set the caption text to the alt text of the first slide.
    This line of code retrieves the alt text of the current slide, and puts the text inside the #caption div, ready for displaying when the user hovers over the image.
  9. Bind moveRight() and moveLeft() to the swipeLeft and swipeRight events.
    Here we use the jQuery Swipe plugin to let the user swipe left and right on a touch device to move between the slides. If the user swipes right-to-left then we call moveRight() to move to the next slide, and vice-versa.

    IE generates JavaScript errors with this plugin, so we use the browser.msie property of the jQuery object to detect if the browser is IE. If it is, then we skip this chunk of code. It's a bit of a hack but it works!

  10. Bind moveleft() and moveRight() to the "move left" and "move right" keys.
    Finally, we create an anonymous event handler function for the document's keydown event. In this function, we detect if either the "move left" or "move right" key was pressed. If it was then we call moveLeft() or moveRight() as required.

Step 7: Create the handleSlideLoad() function

Gallery screenshot

handleSlideLoad() is called whenever a slide image has finished loading. If there are 10 slides in your gallery, it will be called 10 times.

This function does a few different jobs. It records the newly-loaded slide's width, and expands the gallery div's width to accommodate the width of the slide. It also tracks how many slides have now preloaded. If enough slides have loaded then it fades in and activates the gallery.

Here's the code for the handleSlideLoad() function:

// Process each slide once it's finished loading

function handleSlideLoad() {

  // Record the slide's width in the slideWidths array
  slideWidths[$(this).data('slideNum')] = $(this).width();

  // Increase the gallery div’s width to encompass this newly-loaded slide
  $('#gallery').width( $('#gallery').width() + $(this).width() + slideHorizMargin*2 );

  // Record the fact that this slide has loaded in the slideLoaded array
  slideLoaded[$(this).data('slideNum')] = true;

  // Are we still preloading the slides?

  if ( loading ) {

    // Yes: Calculate how many slides we’ve now preloaded

    var preloaded = 0;

    for ( var i=0; i < preloadSlides; i++ ) {
      if ( slideLoaded[i] ) preloaded++;

    // If we’ve preloaded enough slides, fade in the gallery and enable the left/right buttons

    if ( preloaded == preloadSlides || preloaded == totalSlides ) {
      $('#loading').clearQueue().stop().fadeTo('slow', 0 );
      $('#gallery').fadeTo('slow', 1 );
      loading = false;

  // If this newly-loaded slide is the first slide in the gallery,
  // centre it in the browser viewport and set its opacity to currentSlideOpacity.
  // Otherwise, set its opacity to backgroundSlideOpacity.

  if ( $(this).data('slideNum') == 0 ) {
    $(this).fadeTo( 'slow', currentSlideOpacity );
  } else {
    $(this).fadeTo( 'slow', backgroundSlideOpacity );


Let's step through this function:

  1. Record the slide's width in the slideWidths array.
    Since the image has now loaded, we can find out its width in pixels by calling the jQuery width() method on it. We store this value in the slideWidths array, keyed by the slide's index number, for later use.
  2. Increase the gallery div's width to encompass this newly-loaded slide.
    Our gallery div needs to be at least as wide as the total of all the slide widths (plus margins), otherwise the images will wrap. This line of code increases the width of the gallery to allow for the slide's width, plus the horizontal margin either side of the slide.
  3. Record the fact that this slide has loaded in the slideLoaded array.
    We set the slideLoaded array value for this slide to true. We'll use this array next to determine if enough slides have been preloaded to display the gallery.
  4. Display the gallery if enough slides have preloaded.
    The code in the if ( loading ) { ... } block loops through the first few slides (determined by the preloadSlides setting). If all those slides have now preloaded, we enable the gallery.

    To enable the gallery, we first cancel the loading message animation and fade it out slowly. Then we reset the gallery's top CSS property to bring it back into the viewport, and fade it in slowly. We also reset the left and right button heights to enable them, and display the right button. We call addSlideHover(), which adds hover event handlers to the current slide so that the caption will display when the mouse hovers over the slide. Finally, we set the loading global variable to false to indicate that preloading has finished and the gallery is running.
  5. Centre the slide if necessary, and set the slide's opacity.
    The last chunk of code in the function checks to see if we're working with the first slide in the gallery. If we are then we centre it by calling centreCurrentSlide(), and set its opacity to currentSlideOpacity, which is 1 (fully opaque) by default. If we're working with a different slide then we set its opacity to backgroundSlideOpacity (0.5 by default).

Step 8: Create the moveLeft() and moveRight() functions

Our next 2 functions do the actual sliding of the gallery to move from one image to the next. Let's look at moveLeft() first:

// Move one slide to the left by sliding the gallery left-to-right

function moveLeft() {

  // Don't move if this is the first slide, or if we don't yet have a width for the previous slide
  if ( currentSlide == 0 ) return;
  if ( slideWidths[currentSlide-1] == undefined ) return;

  // Cancel all event handlers on the current slide

  // Stop any fades on the caption and hide it

  // Slide the whole gallery right so that the previous slide is now centred
  var offset = slideWidths[currentSlide]/2 + slideHorizMargin*2 + slideWidths[currentSlide-1]/2;
  $('#gallery').animate( { left: '+=' + offset } );

  // Fade the old slide to backgroundSlideOpacity, and the new slide to currentSlideOpacity
  slides[currentSlide].animate( { opacity: backgroundSlideOpacity } );
  slides[currentSlide-1].animate( { opacity: currentSlideOpacity } );

  // Update the current slide index

  // Update the shown/hidden states of left/right buttons as appropriate

  // Set the caption to the new current slide’s alt text,
  // and attach the hover events to the new slide
  $('#caption').html( slides[currentSlide].attr('alt') );

Here's how the function works:

  1. Exit if we can't move anything.
    If we're currently viewing the first slide in the gallery, or if we don't yet have a width for the previous slide (because it hasn't yet preloaded), then we can't move to the previous slide. In these cases, we simply use return to exit the function.
  2. Cancel all event handlers on the current slide.
    Before moving to the next slide, we remove any mouseenter, mouseleave, and touchstart event handlers from the current slide. These handlers display the slide's caption if the user hovers over the image (or touches it on a touch device). Since we're about to move to another slide, we no longer want these handlers on the current slide.
  3. Stop any fades on the caption and hide it.
    We call the jQuery stop() method on the caption to stop any currently-running fade animation on the caption text. We then call clearQueue() to cancel any queued fade in/out animations, and hide the caption by calling hide().
  4. Slide the whole gallery right so that the previous slide is now centred.
    We're now ready to slide our gallery to the previous image. First we calculate the amount we have to slide it; this value is half the width of the current slide, plus the left margin on the current slide, plus the right margin on the previous slide, plus half the width of the previous slide. We store this value in offset, then use the jQuery animate() method to slide the whole #gallery div right by the offset value.
  5. Fade the old slide out, and the new slide in.
    We call animate() on both the "current" slide (which is now the previous slide), and the slide to the left of the current slide (which is the new current slide). These calls to animate() fade the opacities of the 2 slides to backgroundSlideOpacity and currentSlideOpacity respectively.
  6. Update the current slide index.
    Next we decrement the value of the currentSlide global variable to reflect the fact that we've moved to the previous slide.
  7. Update the shown/hidden states of left/right buttons as appropriate.
    We also call our setButtonStates() function to update the states of the left and right buttons if necessary. For example, if we've now moved to the first slide in the gallery then we need to disable the left button.
  8. Set up the caption for the new slide.
    Finally, we replace the caption's text with the new slide's alt text, and reattach the mouseenter, mouseleave, and/or touchstart events to the new slide by calling our addSlideHover() function.

Our moveRight() function is, as you'd imagine, pretty much the mirror image of moveLeft():

// Move one slide to the right by sliding the gallery right-to-left

function moveRight() {

  // Don't move if this is the final slide, or if we don't yet have a width for the next slide
  if ( currentSlide == totalSlides - 1 ) return;
  if ( slideWidths[currentSlide+1] == undefined ) return;

  // Cancel all event handlers on the current slide

  // Stop any fades on the caption and hide it

  // Slide the whole gallery left so that the next slide is now centred
  var offset = slideWidths[currentSlide]/2 + slideHorizMargin*2 + slideWidths[currentSlide+1]/2;
  $('#gallery').animate( { left: '-=' + offset } );

  // Fade the old slide to backgroundSlideOpacity, and the new slide to currentSlideOpacity
  slides[currentSlide].animate( { opacity: backgroundSlideOpacity } );
  slides[currentSlide+1].animate( { opacity: currentSlideOpacity } );

  // Update the current slide index

  // Update the shown/hidden states of left/right buttons as appropriate

  // Set the caption to the new current slide’s alt text,
  // and attach the hover events to the new slide
  $('#caption').html( slides[currentSlide].attr('alt') );

This function slides the whole gallery left to move the next slide to the centre of the window.

Step 9: Create the centreCurrentSlide() function

The centreCurrentSlide() function centres the current slide horizontally in the browser viewport. We call it in 2 ways:

  1. From the handleSlideLoad() function, to centre the first slide in the viewport once it's loaded.
  2. As an event handler for the browser window's resize event, so we can re-centre the slide whenever the user resizes their browser.

Here's the code for the function:

// Centre the current slide horizontally in the viewport

function centreCurrentSlide() {

  // Work out how far the left edge of the slide is from the
  // left hand edge of the gallery div

  var offsetFromGalleryStart = 0;

  for ( var i=0; i<currentSlide; i++ ) {
    offsetFromGalleryStart += slideWidths[i] + slideHorizMargin*2;

  // Find the horizontal centre of the browser window
  var windowCentre = $(window).width() / 2;

  // Compute the left position of the slide based on the window centre and slide width
  var slideLeftPos = windowCentre - ( slideWidths[currentSlide] / 2 );

  // Compute the offset for the gallery div based on the slide position and
  // the slide offset from the gallery start. Also allow for the
  // horizontal margin on the left side of the slide.
  var offset = slideLeftPos - offsetFromGalleryStart - slideHorizMargin;

  // Move the gallery div to the new offset
  $('#gallery').css( 'left', offset );

Most of this function is simple maths to work out the position of the current slide relative to both the window centre and the start of the #gallery div. Once we know this, we can simply readjust the position of the #gallery div to re-centre the slide.

Here's how it works:

  1. Work out how far the slide is from the left hand edge of the gallery div.
    First we need to calculate the position of the left edge of the slide, relative to the left edge of the gallery. To do this, we use a loop to add up all the widths of the slides before the current slides, plus each slide's left and right margins. We store the result in offsetFromGalleryStart.
  2. Find the horizontal centre of the browser window.
    This is easy enough — it's the window's width divided by 2. We store the result in windowCentre.
  3. Compute the left position of the slide.
    Now we work out the desired position of the slide's left edge, relative to the browser window. This is the window centre minus half the slide width. We store this in slideLeftPos.
  4. Compute the offset for the gallery div.
    We're now ready to work out the x-position that we need to move the gallery div to. This is the value of slideLeftPos minus the value of offsetFromGalleryStart. We also need to subtract slideHorizMargin to compensate for the left hand margin on the slide.
  5. Move the gallery div to the new offset.
    Now that the maths is out of the way, we just need to move our gallery div to the new position. We do this by setting the gallery's left CSS property to the new offset value.

Step 10: Create the setButtonStates() function

Right button

setButtonStates() is very simple. Its sole job is to show or hide the left and right buttons based on the currently-displayed slide:

// Show or hide the left and right buttons depending on the current slide:
// 1. If we're showing the first slide, hide the left button
// 2. If we're showing the last slide, hide the right button

function setButtonStates() {

  if ( currentSlide == 0 ) {
  } else {

  if ( currentSlide == totalSlides - 1 ) {
  } else {


This should be self-explanatory. We use the jQuery hide() and show() methods to hide and show each button as appropriate.

Step 11: Create the addSlideHover() function

This function adds various event handlers to the current slide, allowing the user to view the caption by hovering over the slide with their mouse — or, in the case of touch devices, by tapping the slide:

// Attach mouseenter and mouseleave event handlers to the current slide to fade the caption in and out
// However, if the device supports touch events then fade the caption in/out when the slide is touched

function addSlideHover() {

  if ( 'ontouchstart' in document.documentElement ) {
    slides[currentSlide].bind( 'touchstart', function() {
      if ( $('#caption').is(':visible') ) {
        $('#caption').stop().clearQueue().fadeOut( captionSpeed );
      } else {
        $('#caption').stop().clearQueue().fadeTo( captionSpeed, captionOpacity );
    } );
  } else {
      function() { $('#caption').stop().fadeTo( captionSpeed, captionOpacity ) },
      function() { $('#caption').stop().fadeTo( captionSpeed, 0 ) }

The function first checks if the browser supports the touchstart event. If it does then it creates a touchstart event handler that toggles the caption by fading it out if it's already visible, or fading it in if it's not.

There is a jQuery method, fadeToggle(), that can do this toggling action for you. However, fadeToggle() doesn't let you fade in to a specific opacity value. It only fades right out (opacity=0) or right in (opacity=1).

If the browser doesn't support touchstart then we instead call the jQuery hover() method to set up 2 event handler functions: one for the mouseenter event, and one for the mouseleave event. These functions fade the caption in and out as the mouse moves over and out of the image.

Step 12: Create the fadeInLoadingMessage() and fadeOutLoadingMessage() functions

The last couple of functions in our script set up the pulsing animation for the "Please wait..." loading message:

// Functions to pulse the loading message

function fadeInLoadingMessage() {
  $('#loading').animate( { opacity: loadingMessageMaxOpacity }, loadingMessageSpeed, 'swing', fadeOutLoadingMessage );

function fadeOutLoadingMessage(){
  $('#loading').animate( { opacity: loadingMessageMinOpacity }, loadingMessageSpeed, 'swing', fadeInLoadingMessage );

fadeInLoadingMessage() uses animate() to set up a jQuery animation that fades the loading message up to its maximum opacity. Once the fade-in is complete, it triggers the fadeOutLoadingMessage() function.

fadeOutLoadingMessage() does the reverse: it fades out the message, then calls fadeInLoadingMessage().

In this way, we set up an animation loop that pulses the loading message continuously in and out, until it is stopped by the calls to clearQueue() and stop() inside the handleSlideLoad() function.

All done!

We've now built our lovely sliding image gallery! Here's the demo again:

I hope you've found this tutorial useful, and you enjoy using the gallery. If you have any comments or questions on the tutorial, feel free to post a response below. Have fun!

Follow Elated

Related articles

Responses to this article

20 most recent responses (oldest first):

29-Mar-12 03:13
@azukizero: I guess you could put a <p> after each <img> that contains the caption. Then you could create a slideCaptions array and populate it in the same way as the slides array:

$('#gallery p').each( function() {
$(this).data( 'slideNum', totalCaptions );
slideCaptions[totalCaptions++] = $(this);
} );

Then you could set the current caption throughout the code like this:

$('#caption').html( slideCaptions[currentSlide].html() );

Haven't tested it but you get the idea!
16-Apr-12 03:55
Great writeup and easy to understand. Thanks a lot!!

I have two questions though.
1) For each image I want on hover to add elements like facebook like, tweet/share etc and comments. How will that be possible?

2) I don't see any button where I can exit from the gallery. My intention is to click from a list of images and get to this gallery and then click close and go back to the list.

Can you please advise?
04-May-12 05:02

1) To like or share an image it will need its own unique URL. You could maybe do this by hacking the gallery code to accept a hash value in the URL and display the corresponding image when it loads. Or you could let the user click an image to open it in its own unique page, where it could then be liked/shared.

2) Just create a link back to your image list. (Or create a button, and add an onclick that returns the user to the image list.)
04-May-12 05:07
@matt thanks for the reply! I will give it a try, not sure if I will achieve it

21-May-12 02:45
thanks for this it's amazing...
i just have 1 question, how can i make it slide automatically??
22-May-12 05:20
Read the fifth post in the thread.
28-Dec-12 21:17
Firstly, I love this Slider! Thank you for taking the time and energy to create and share it.

I am brand new to jquery and javascript so pardon if these questions have commonly known answers.

I am currently auto-playing the slideshow using "var timer = setInterval( moveRight, 4500 );" I am not sure if this is the best way to go about it. Is it? If it is, then how can I get the right and left buttons to override the setInterval timing? Currently, even when I press the buttons, it still wants to pull the next slide in from right to left, every 4500 ms, no matter which direction I'm going in, and regardless of when I just pressed the right/left arrow button. I am okay with the auto-play feature being turned off once the user hits the left/right arrows. I just don't know how to do it.

It would also be nice if the user could pause the playback and resume it again with the space-bar. How can I do that?

Also, I would like the slideshow to infinitely loop back to the first image as if there was no break in the loop. I have found a way to rig it but simply listing the images again, but this will only "loop" until the images are done being shown, not infinitely.

Last but not least, how can I set the time it takes to switch out each slide to be 1000 or 1500 ms?

Thank you so much for your help!

[Edited by LittleMama1980 on 28-Dec-12 22:09]
13-Jan-13 22:54
@LittleMama1980: To turn off your auto-play feature, just call clearInterval( timer ) .

16-Apr-13 22:33
i knw how for css we make a different file with .css extension.. but m unaware of jquery ,ajax.. i need help to let me knw wer to put jquery and ajax codes
10-Sep-13 10:51
Hi , i cant use it with reflection on ie8 can anybody help me ?
11-Nov-13 07:09
Hi Mark,

Thanks so much, this is exactly what I was looking for.
Just a quick question, would it possible to make the images fully responsive and perhaps move the scrolling vertically for mobile device?
01-Feb-14 13:03
Thanks Matt,
I want to know how to move text on the slide to slide?
31-Mar-14 04:42
I do well

var currentSlide = 4;


if ( $(this).data('slideNum') == 4) {
$(this).fadeTo( 'slow', currentSlideOpacity );
} else {
$(this).fadeTo( 'slow', backgroundSlideOpacity );

and it does not work, I can not boot from a specific slide (here the 4)

Do u have any solution ?
31-Jul-15 23:59
Heya! It's me again, a couple years later. My slider is suddenly not centering in regular browsers, but still looking great in mobile browsers. Any idea what's going on with the code to make this new off-center issue? I haven't changed the slider code since before the new issue started. The website is
01-Aug-15 10:22
29-Aug-15 03:12
Next and previous button is not working also loading only one picture.
Can you give some hint where look for error.

29-Aug-15 15:24
"Can you give some hint where look for error."

Sure! No problem.

Somewhere in your code, 'cause it works in the original.
29-Aug-15 19:17
chrishirst, your comments are not helpful. Please stop replying to people and let the original poster address these things if he wants to.

Matt, do you think you can chime in and help us out? I know this an old post but it is still very useful to a lot of people! haven't seen a reply from you in awhile. Thanks in advance!
30-Aug-15 06:18
My post was precisely as helpful as YOUR QUESTION IS.

How exactly is ANYONE supposed to diagnose what you have done wrong by looking at the original code THAT WORKS????

Read THIS:

Currently there are only two people answering topics here and NEITHER of us are mindreaders, HOW can we know why YOUR code does not work??????

The original works, your's doesn't, can YOU guess where the problem is???

[the following is for anyone and everyone reading]

IF you are going to ask a question that you want a really useful answer to THEN:

Either show us YOUR CODE or provide a URI that we can maybe use to diagnose what is wrong and where it is going wrong.

And no, codepen, jsfiddle and other such 'tools' are of no use, in this situation. Neither is showing us a picture. We need YOUR CODE and YOUR images to be able to test the problem.

If you do not have hosting, sign up for a free account at Hostinger ( or 2freehosting (
Yes they are referral links, mine.

Or if you want serious hosting, tell me and I'll send you a proforma invoice for a account (Yep that's me as well)

Thank you for your attention and participation at Elated.

[Edited by chrishirst on 30-Aug-15 06:18]
31-Aug-15 23:29
I am really new to web developer (and also js, css, and html).
What I am about to do is to make auto-scroll image gallery using this tutorial.
Could anyone tell me how to
1. auto-scroll this image gallery to right and
2. when it reached the end of image it will return to first image and
3. then redo the scroll (unlimited loop)

What I have been doing is this:

//new function to loop the scroll (well, it didnt work out)

function doLoop()
function moveLoop()
//console.log("moveLoop currentSlide:"+currentSlide);
//console.log("moveLoop preloadSlides:"+preloadSlides);
var ts = 4000;
var mult = 1;
var finish = false;
for (i=0; i<= preloadSlides; i++)
console.log("NOT Finnish");
setTimeout(moveRight, ts*mult);
finish = true
setTimeout(doLoop, ts*(mult-2));
finish = false;

//and on handleSlideLoad()
if ( preloaded == preloadSlides || preloaded == totalSlides )
$('#loading').clearQueue().stop().fadeTo('slow', 0 );
$('#gallery').fadeTo('slow', 1 );
loading = false;
moveLoop(); //<----added this

It is not a loop, because when it reached the end of index (last image) I reload the page using doLoop() function.
But this far it's acceptable because I put it on the iFrame, so users won't notice it's reloaded.
Can anyone point me to more proper way to do it?

[Edited by noobsee on 01-Sep-15 04:50]

View all 67 responses »

Post a response

Want to add a comment, or ask a question about this article? Post a response.

To post responses you need to be a member. Not a member yet? Signing up is free, easy and only takes a minute. Sign up now.

Top of Page