How to create an image slide show on a webpage

Websites that include many images, such as art or photo gallery sites, can benefit by presenting some (or all) of their images in slide show format. Here we talk about how to use and create a cross-browser compatible JavaScript file (slideShow.js) that produces a slide show in which images fade in an out as specified by a given list of <img> elements.

Note  Because addEventListener is used, this content is only applicable to Windows Internet Explorer 9 and later. Prior to Internet Explorer 9, attachEvent must be used. For more info, see How to Create Effective Fallback Strategies.

 

This topic is divided into two major sections:

  • How to use slideShow.js to create a slide show from a list of img tags
  • Implementation details of slideShow.js

Using slideShow.js

This example shows the simplest markup needed to produce such a slide show:

<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="X-UA-Compatible" content="IE=Edge"> <!-- For intranet testing only, remove in production. -->
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  <title>Slide Show</title>
</head>

<body>
  <div id="slideShowImages">
    <img src="images/slide1.jpg" alt="Slide 1" />
    <img src="images/slide2.jpg" alt="Slide 2" />
    <img src="images/slide3.jpg" alt="Slide 3" />    
    <img src="images/slide4.jpg" alt="Slide 4" />
  </div>  
  <button id="slideShowButton"></button> <!-- Optional button element. -->
  <script src="slideShow.js"></script>
</body>

</html>

To use slideShow.js to create a slide show, the follow markup must be present:

  • A <div> wrapper, whose ID is exactly slideShowImages, containing <img> elements that point to the desired slide show images.
  • A script element pointing to slideShow.js.

Note  

 

This example includes an optional slide show on/off toggle button. If you use this toggle button, its ID must be slideShowButton. If you don't use this toggle button, the user can stop or start the slide show by clicking any one of the cycling slide show images.

Additionally, if you use slideShow.js, you can easily style the slide show <div> wrapper along with its child images as shown in this code sample:

<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="X-UA-Compatible" content="IE=Edge"> <!-- For intranet testing only, remove in production. -->
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  <title>Slide Show</title>
  <style>
    #slideShowImages { /* The following CSS rules are optional. */
      border: 1px gray solid;
      background-color: lightgray;
    }   
  
    #slideShowImages img { /* The following CSS rules are optional. */
      border: 0.8em black solid;
      padding: 3px;
    }   
  </style>
</head>

<body>
  <div id="slideShowImages">
    <img src="images/slide1.jpg" alt="Slide 1" />
    <img src="images/slide2.jpg" alt="Slide 2" />
    <img src="images/slide3.jpg" alt="Slide 3" />    
    <img src="images/slide4.jpg" alt="Slide 4" />
  </div>  
  <button id="slideShowButton"></button> <!-- Optional button element. -->
  <script src="slideShow.js"></script>
</body>

</html>

One of the nice features of slideShow.js is that, slide images of varying dimensions are automatically centered within the <div> wrapper, and the transitions between the differently sized images look reasonable in that as one slide is faded out, the other is concurrently faded in.

Now you can use slideShow.js to create a slide show from a list of <img> elements. The remainder of this topic is dedicated to explaining the implementation details of slideShow.js.

Note  To change the speed of the slide show or the text of the toggle button, see the Globals section.

 

Implementation details

As can be seen by reviewing slideShow.js, this JavaScript file is composed of essentially two items, the line window.addEventListener('load', slideShow, false) and its associated callback function slideShow.

Because the function slideShow references <div id="slideShowImages"> (and possibly <button id="slideShowButton">), we must be sure that these elements are available in the DOM before doing so. This is accomplished via window.addEventListener('load', slideShow, false). In other words, slideShow is called only when the webpage's markup is available from the DOM. Another advantage of placing the majority of slideShow.js's code within a single function is that it protects the namespace of slideShow.js users.

Now that we understand when slideShow is invoked, let's turn to the details of slideShow itself, starting with its core algorithm:

Core algorithm

At the heart of slideShow's algorithm is the CSS layout of <div id="slideShowImages"> and its children <img> elements. This CSS layout is defined within slideShow.js's initializedSlideShowMarkup function. In particular, initializedSlideShowMarkup:

  • Sets the CSS position of <div id="slideShowImages"> to relative. This allows any of its children, which are positioned absolutely, to be positioned relative to this container. If <div id="slideShowImages"> were set to static positioning (the default), its children that are absolutely positioned would instead be positioned relative to the <body> element and not <div id="slideShowImages">.
  • Positions all slide show <img> elements absolutely. This stacks (or piles) all such <img> elements on top of each within their <div id="slideShowImages"> container. Think of the slides as a vertical stack of images with the first slide (the first listed <img> element) on the bottom of the stack and the last slide (the last listed <img> element) on the top of the stack.
  • Changes the opacity of each slide (in the stack) to 0 to make all slides completely transparent. Then, the first <img> element’s opacity is set to 1. This makes the slide on the bottom of the stack visible through all the transparent slides on top of it.

With this initial layout complete, we now move between the stack of slides, starting from the bottom (the first slide) toward the top (the last slide):

  1. Let the current slide (in the stack) be the bottom-most slide (the first <img> element).
  2. Move to the next slide in the stack. If there is no next slide, let the next slide be the first slide.
  3. Set the opacity value of the current slide to 1 (visible) and the opacity value of the next slide to 0 (transparent).
  4. Concurrently and "slowly" change the opacity value of the current slide from 1 to 0 and the opacity value of the next slide from 0 to 1. This "slowly" fades out the current slide while fading in the next slide.
  5. Let the current slide be the next slide.
  6. Go to step 2 until the user signals that the slide show should be stopped.

With this core algorithm understood, we can now proceed to the details of the primary components of the slideShow function, which includes these components:

  • Globals
  • Main
  • initializeGlobals
  • insufficientSlideShowMarkup
  • initializeSlideShowMarkup
  • maxSlideWidth and maxSlideHeight
  • startSlideShow
  • haltSlideShow
  • toggleSlideShow
  • transitionSlides and fadeActiveSlides

Globals

The first item in slideShow is an object literal called globals. This object is used to contain all of slideShow's "global variables" in one obvious (and handy) place. For example, to access slideDelay from any place within slideShow, just invoke globals.slideDelay.

The first six of these "global variables" are of particular interest:

slideDelay: 4000,
fadeDelay: 35,
wrapperID: "slideShowImages",
buttonID: "slideShowButton",
buttonStartText: "Start Slides",
buttonStopText: "Stop Slides",

The first item, slideDelay, determines the amount of time, in milliseconds, that any given slide is visible on the screen. In this case, each slide will be visible for a total of 4 seconds.

To describe fadeDelay, we refer back to the core algorithm used in slideShow.js. Consider our stack of slide images. The slide on the bottom of the stack has an opacity value of 1 (is visible) while all others have an opacity value of 0 (transparent). To perform a fade operation between the first slide and the next slide, we decrease the opacity of the first slide from 1 to 0, while at the same time, increasing the opacity of the second from 0 to 1. Precisely half way through this operation, both slides will have an opacity value of 0.5. At the end of this operation, the first slide has an opacity value of 0 (transparent) and the second an opacity value of 1 (visible). With this understanding in place, fadeDelay represents the amount of time between individual opacity changes. For example, if fadeDelay is 35, there will be 35 milliseconds between each opacity change. The opacity change amount is the reciprocal of this value, which in this case, is about 0.0286. Because the opacity increment value is 1/35, it will take 35 such values to increment 0 to 1 (and similarly, 35 such values to decrement 1 to 0). Thus, the total amount of time it takes to complete one fade operation is 35 milliseconds times 35 or 35² = 1225 milliseconds = 1.225 seconds. Likewise, if fadeDelay were 45, it would take 45² = 2.025 seconds to complete a single fade operation between two consecutive slides. Bottom line, adjust slideDelay and fadeDelay to your liking but always keep fadeDelay significantly smaller than slideDelay.

Moving on, wrapperID and buttonID must match the ID values used in the markup: <div id="slideShowImages"> and <button id="slideShowButton">.

buttonStartText and buttonStopText define the text to be used in the (optional) slide show toggle button. You can change these labels (strings) to whatever fits the need of your app.

For information about the remaining "global variables,"see the comments in the slideShow.js file.

Main

Now we'll describe the "main" code block of the slideShow function:

initializeGlobals();  

if ( insufficientSlideShowMarkup() ) {
  return;
}

 // Assert: there's at least one slide image.

if (globals.slideImages.length == 1) {
  return;
}

// Assert: there are at least two slide images.

initializeSlideShowMarkup();

globals.wrapperObject.addEventListener('click', toggleSlideShow, false);

if (globals.buttonObject) {
  globals.buttonObject.addEventListener('click', toggleSlideShow, false);
} 

startSlideShow();

This main code block can be described as follows:

  1. initializeGlobals initializes globals, providing access to the <div> wrapper element, its <img> children, and the toggle button element (if present).
  2. insufficientSlideShowMarkup returns false if the markup expected by slideShow.js appears to be present, true otherwise. As can be seen in the code example, if true is returned, the slideShow function is immediately exited, and silently terminates slideShow.js (as by design).
  3. If globals.slideImages.length == 1 is true, there's only one slide show <img> element in markup and it's already onscreen; we simply terminate slideShow.js (leaving this solo image onscreen).
  4. initializeSlideShowMarkup prepares the given slide show markup (as described in the core algorithm section) for the forthcoming slide show.
  5. We next add an event listener, toggleSlideShow, such that when the <div> wrapper element is clicked, the slide show is toggled off (if on) and on (if off).
  6. Next, if globals.buttonObject is true, the slide show toggle button is present in markup and we attach the same event listener function (toggleSlideShow) to it.
  7. Finally, we call startSlideShow to start the slide show in earnest.

Each of the functions described here, as well as all remaining functions contained within slideShow, are described, in detail, next.

initializeGlobals

This is the code for the nested function initializeGlobals:

function initializeGlobals() {   
  globals.wrapperObject = (document.getElementById(globals.wrapperID) ? document.getElementById(globals.wrapperID) : null);
  globals.buttonObject = (document.getElementById(globals.buttonID) ? document.getElementById(globals.buttonID) : null);   
  
  if (globals.wrapperObject) {
    globals.slideImages = (globals.wrapperObject.querySelectorAll('img') ? globals.wrapperObject.querySelectorAll('img') : []);
  }
}

Based on the given ID values contained in globals (that is, gloabals.wrapperID and globals.butonID), we obtain a reference to them if they're present in markup. Then, using querySelectorAll, we fill an array with slide <img> objects. This array, globals.slideImages, works as our metaphorical stack as described in the core algorithm section.

insufficientSlideShowMarkup

This is the code for the nested function insufficientSlideShowMarkup:

function insufficientSlideShowMarkup() {
  if (!globals.wrapperObject) { 
    if (globals.buttonObject) {
      globals.buttonObject.style.display = "none"; 
    }
    return true;
  }

  if (!globals.slideImages.length) { 
    if (globals.wrapperObject) {
      globals.wrapperObject.style.display = "none"; 
    }
  
    if (globals.buttonObject) {
      globals.buttonObject.style.display = "none"; 
    }
  
    return true;
  }
  
  return false; 
} 

The primary purpose of this function is to return false if all the expected markup is present, and true otherwise. As you can see in this code, if it's determined that not all the expected markup is present, the code tries to clean up the associated slide show markup (that is, sets the value of the CSS display property to "none",) before returning true.

initializeSlideShowMarkup

This is the code for the nested function initializeSlideShowMarkup:

function initializeSlideShowMarkup() {  
  var slideWidthMax = maxSlideWidth(); 
  var slideHeightMax = maxSlideHeight();
  
  globals.wrapperObject.style.position = "relative";
  globals.wrapperObject.style.overflow = "hidden"; 
  globals.wrapperObject.style.width = slideWidthMax + "px";
  globals.wrapperObject.style.height = slideHeightMax + "px";
  
  var slideCount = globals.slideImages.length;
  for (var i = 0; i < slideCount; i++) { 
    globals.slideImages[i].style.opacity = 0;
    globals.slideImages[i].style.position = "absolute";
    globals.slideImages[i].style.top = (slideHeightMax - globals.slideImages[i].getBoundingClientRect().height) / 2 + "px";   
    globals.slideImages[i].style.left = (slideWidthMax - globals.slideImages[i].getBoundingClientRect().width) / 2 + "px";               
  }
  
  globals.slideImages[0].style.opacity = 1; 
      
  if (globals.buttonObject) {
    globals.buttonObject.textContent = globals.buttonStopText;
  }
}

This function performs a number of actions, including:

  • Sets the CSS position values of <div id="slideShowImages"> and its <img> children, as described in core algorithm section.

  • Adjusts the width and height of the <div id="slideShowImages"> container so that the slide image(s) with the widest width and tallest height will fit exactly within it (in case the slide images have differing dimensions).

  • Adjusts the absolute positioning of the slide images so that they're centered within the <div id="slideShowImages"> container (again, in case the slide images have inhomogeneous dimensions). This is done by pushing any given smaller image away from the top-left corner of its container by ½ of the margin below and to the right of it:

  • Lastly, if the toggle button is present in markup, the function sets the toggle button's "stop the slide show" text (in that, by default, the slide show is initially running).

maxSlideWidth and maxSlideHeight

These two functions return the width of the widest slide image and the height of the tallest slide image (which might be two different images). Because both functions are nearly identical, we'll only look at maxSlideWidth:

function maxSlideWidth() {
  var maxWidth = 0;
  var maxSlideIndex = 0;
  var slideCount = globals.slideImages.length;
  
  for (var i = 0; i < slideCount; i++) {
    if (globals.slideImages[i].width > maxWidth) {
      maxWidth = globals.slideImages[i].width;
      maxSlideIndex = i;
    }
  }

  return globals.slideImages[maxSlideIndex].getBoundingClientRect().width;
}

The getBoundingClientRect method always returns a value in pixels and includes all specified CSS properties such as borders, margins, and so forth. This ensures that <div id="slideShowImages"> is wide and tall enough to accommodate all such CSS additions applied to the slide images.

startSlideShow

The code for the nested function startSlideShow is straightforward:

function startSlideShow() {
  globals.slideShowID = setInterval(transitionSlides, globals.slideDelay);                
}

Here we simply request that the function transitionSlides be called every gloabls.slideDelay milliseconds until such repetitive invocations are halted by calling clearInterval(globals.slideShowID).

haltSlideShow

As you may have anticipated, the code for haltSlideShow is composed of a single line:

function haltSlideShow() {
  clearInterval(globals.slideShowID);   
}

This clears the previously requested invocations, every globals.slideDelay milliseconds, of transitionSlides, halting the slide show.

toggleSlideShow

This is the code for the nested function toggleSlideShow:

function toggleSlideShow() {
  if (globals.slideShowRunning) {
    haltSlideShow();
    if (globals.buttonObject) { 
      globals.buttonObject.textContent = globals.buttonStartText; 
    }
  }
  else {
    startSlideShow();
    if (globals.buttonObject) { 
      globals.buttonObject.textContent = globals.buttonStopText; 
    }            
  }
  globals.slideShowRunning = !(globals.slideShowRunning);
}

Recall the two addEventListener calls made in the main code block:

globals.wrapperObject.addEventListener('click', toggleSlideShow, false); 
  
if (globals.buttonObject) {
  globals.buttonObject.addEventListener('click', toggleSlideShow, false);
}

Thus, toggleSlideShow is invoked whenever <div id="slideShowImages"> or <button id="slideShowButton"> (if present) are clicked.

Now because globals.slideShowRunning is initially set to true, on the first invocation of toggleSlideShow, haltSlideShow is called and (if present) the text for the toggle button is changed to its "start the slide show" form. Then, globals.slideShowRunning is negated, which indicates the current state of the slide show (off, in this case).

When toggleSlideShow is called again (when a slide show image or the toggle button is clicked), globals.slideShowRunning is false, so startSlideShow is invoked, the button text is switched to its "shut off the slide show" form, and globals.sideShowRunning is negated (indicating that the slide show is now on/running).

Stated more succinctly, toggleSlideShow does just that - it turns the slide show on and off by calling startSlideShow and haltSlideShow, as determined by the current state of globals.slideShowRunning.

transitionSlides and fadeActiveSlides

The core algorithm is primarily implemented in the transitionSlides function:

function transitionSlides() {
  var currentSlide = globals.slideImages[globals.slideIndex];
  
  ++(globals.slideIndex);
  if (globals.slideIndex >= globals.slideImages.length) {
    globals.slideIndex = 0;
  }
  
  var nextSlide = globals.slideImages[globals.slideIndex];
  
  var currentSlideOpacity = 1;
  var nextSlideOpacity = 0;
  var opacityLevelIncrement = 1 / globals.fadeDelay;
  var fadeActiveSlidesID = setInterval(fadeActiveSlides, globals.fadeDelay);
  
  function fadeActiveSlides() {
    currentSlideOpacity -= opacityLevelIncrement;
    nextSlideOpacity += opacityLevelIncrement;
    
    // console.log(currentSlideOpacity + nextSlideOpacity);
    
    if (currentSlideOpacity >= 0 && nextSlideOpacity <= 1) {
      currentSlide.style.opacity = currentSlideOpacity;
      nextSlide.style.opacity = nextSlideOpacity; 
    }
    else {
      currentSlide.style.opacity = 0;
      nextSlide.style.opacity = 1; 
      clearInterval(fadeActiveSlidesID);
    }        
  }
}

Recall that globals.slideImages is an array of slide image objects and that globals.slideIndex is initially set to 0. With this in mind, here's the pseudocode for transitionSlides:

  1. Let the current slide be the one indicated by globals.slideIndex:

    var currentSlide = globals.slideImages[globals.slideIndex];
    
  2. Move to the next slide and if there is no next slide, start from the beginning:

    ++(globals.slideIndex);
    if (globals.slideIndex >= globals.slideImages.length) {
      globals.slideIndex = 0;
    }
    
  3. Let the next slide be the one indicated by globals.slideIndex:

    var nextSlide = globals.slideImages[globals.slideIndex];
    
  4. Prepare the local variables used to fade out the current slide and fade in the next slide:

    var currentSlideOpacity = 1;
    var nextSlideOpacity = 0;
    var opacityLevelIncrement = 1 / globals.fadeDelay;
    
  5. Call the local function fadeActiveSlides every globals.fadeDelay milliseconds until both slides have completed faded. That is, fadeActiveSlides is invoked every globals.fadeDelay milliseconds while currentSlideOpacity >= 0 and nextSlideOpacity <= 1. If one of these becomes false, both slides are, for all practical purposes, fully faded, and so we explicitly set their opacity values and terminate the repetitive invocations of fadeActiveSlides in the else clause:

    currentSlide.style.opacity = 0;
    nextSlide.style.opacity = 1; 
    clearInterval(fadeActiveSlidesID);
    

As the code for fadeActiveSlides shows, the current slide's opacity is decreased by the same amount that the next slide's opacity is increased, and because opacity values are always between 0 and 1, it follows that the sum of these two opacity values must always be 1 (or at least very close to 1, given rounding errors). This, in fact, is true and can be verified by calling console.log(currentSlideOpacity + nextSlideOpacity) as suggested by the comment in the previous code example, and repeated here:

// console.log(currentSlideOpacity + nextSlideOpacity);

For more info about console.log, see How to use F12 Developer Tools to Debug your Webpages.

CSS role in implementation of slideShow.js

Paradoxically, it's the CSS that allows for the implementation of this JavaScript slide show library (slideShow.js). In particular, setting <div id="slideShowImages">'s CSS position value to relative and all its <img> children to a position value of absolute allows for a "stack" of transparent slide images. It then becomes a matter of tweaking the opacity values of adjacent slides to create a fading-style slide show.

Finally, there are a many different ways to implement a JavaScript based slide show library, including various jQuery plugins. By providing a detailed explanation of at least one such implementation, it becomes easier to either write your own customized slide show library or modify slideShow.js to suit your specific needs (when required).

Contoso Images photo gallery