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 exactlyslideShowImages
, 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">
torelative
. This allows any of its children, which are positioned absolutely, to be positioned relative to this container. If<div id="slideShowImages">
were set tostatic
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):
- Let the current slide (in the stack) be the bottom-most slide (the first
<img>
element). - Move to the next slide in the stack. If there is no next slide, let the next slide be the first slide.
- Set the opacity value of the current slide to 1 (visible) and the opacity value of the next slide to 0 (transparent).
- 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.
- Let the current slide be the next slide.
- 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:
initializeGlobals
initializesglobals
, providing access to the<div>
wrapper element, its<img>
children, and the toggle button element (if present).insufficientSlideShowMarkup
returnsfalse
if the markup expected byslideShow.js
appears to be present,true
otherwise. As can be seen in the code example, iftrue
is returned, theslideShow
function is immediately exited, and silently terminatesslideShow.js
(as by design).- If
globals.slideImages.length == 1
istrue
, there's only one slide show<img>
element in markup and it's already onscreen; we simply terminateslideShow.js
(leaving this solo image onscreen). initializeSlideShowMarkup
prepares the given slide show markup (as described in the core algorithm section) for the forthcoming slide show.- 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). - Next, if
globals.buttonObject
istrue
, the slide show toggle button is present in markup and we attach the same event listener function (toggleSlideShow
) to it. - 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
andheight
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
:
Let the current slide be the one indicated by
globals.slideIndex
:var currentSlide = globals.slideImages[globals.slideIndex];
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; }
Let the next slide be the one indicated by
globals.slideIndex
:var nextSlide = globals.slideImages[globals.slideIndex];
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;
Call the local function
fadeActiveSlides
everyglobals.fadeDelay
milliseconds until both slides have completed faded. That is,fadeActiveSlides
is invoked everyglobals.fadeDelay
milliseconds whilecurrentSlideOpacity >= 0
andnextSlideOpacity <= 1
. If one of these becomesfalse
, both slides are, for all practical purposes, fully faded, and so we explicitly set their opacity values and terminate the repetitive invocations offadeActiveSlides
in theelse
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).