Loading images with native JavaScript and handling of events for showing loading spinners

In my other tutorial I described how to load images with jQuery. Sometimes you might need to implement a similar functionality, but without jQuery. This tutorial tells you how to load images with plain old, native JavaScript and how to handle events in order to show and hide loading spinners. I thought I should post my approach to prevent you from "re-inventing the wheel" again and again like it is (unfortunately) done every day in projects worldwide. "Re-inventing the wheel" is waste in most cases, and to me waste should be treated like crime - nobody wants crime! You can use this code in your own projects, but make sure not to forget the attribution...

I have implemented two examples of how to use the loadImage(...) API (see below). The listeners are used to show a loading spinner and to hide it (sometimes referred to as "loading icon" or "busy indicator"). For the overlay functionality I use basic CSS and HTML. However, the loading spinners I display and hide via callback functions work on Chrome, Safari, Firefox and IE9+, IE 7/8 or lower is not supported because I am using rgba in my CSS (I did not check other browsers). But there is also an easy way to display the loading spinner in a cross browser enabled way. To demonstrate this I have also added a separate example, please check the code below for details. The examples should be self explanatory. You could even extend the API the way you want, i.e. allowing to pass an already existing image element or just the id of that image element (I did not add this to the API to keep it simple).

The basic idea behind the scenes of the loadImage() API is:

  • Use the JavaScript Image object
  • Listen for the beforeLoad event to display the loading spinner
  • Listen for the complete event to remove the loading spinner
  • Listen for the load event to display the loaded image
  • Listen for the error event to display a simple error message
  • Trigger load in case the image is loaded from the cache

Please do not get confused about the wording I have used here. I wrote this tutorial down to make it accessible for everybody because I know there are many people out there struggling with the best way to load images with JavaScript. There are many ways to implement the event handlers and performance improvements of the handlers are possible. Although maybe not relevant for this tutorial, if you want to learn what browser reflow is then visit this. By the way: don't forget to leave me a comment or donation if you like this tutorial.

Are you looking for a solution that uses jQuery? This is covered in my other tutorial: Loading images with jQuery and handling of events for showing loading spinners

Enough words, here is the full code of how you can load images with jQuery:

loadImagesWithNativeJavascript.html (demo)
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>DEMO: Loading images with native JavaScript and handling of events for showing loading spinners</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" >
        <meta http-equiv="X-UA-Compatible" content="IE=edge" >

        <style>
            .imageContainer {
                position:relative; height:200px; width:150px; border:1px solid black; margin-top:20px; margin-left: 10px;
            }

            .imageContainer.horizontal{
                display:inline-block; margin-left: 10px;
            }

            /* Attention: our loading spinner is not optimized for IE 7/8 or below, but it works with IE9+ */
            .loadingSpinner {
                position: absolute;
                z-index: 1000;
                top: 0;
                left: 0;
                height: 100%;
                width: 100%;
                background: rgba( 255, 255, 255, .8 ) 
                            url('/assets/img/nabisoft/tutorials/loading-spinner-red.gif') 
                            50% 50% 
                            no-repeat;
            }
            
            /* Cross Browser Loading Spinner */
            .crossBrowserLoadingSpinner {
                position: absolute;
                z-index: 1000;
                top: 0;
                left: 0;
                height: 100%;
                width: 100%;
                opacity: 0.8;
                background-color: white;
                               
                /* IE 8 */
                -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
                /* IE 7 */
                filter: alpha(opacity=80);
                zoom: 1;
            }
            
            .loadingSpinnerImage {
                position: absolute;
                z-index: 1001;
                top: 0;
                left: 0;
                height: 100%;
                width: 100%;
                background: transparent url('/assets/img/nabisoft/tutorials/loading-spinner-red.gif') 50% 50% no-repeat;               
            }
        </style>

    </head>

    <body>
        
        <h1>DEMO: Loading images with native JavaScript and handling of events for showing loading spinners</h1>
            
        <h2>1. Calling the API for a single image URL</h2>
        <div>
            <input id="imageUrl" type="text" value="http://lorempixel.com/150/200/" style="width:300px;"/>
            <button id="loadImageBtn">Load Image</button>
        </div>
        <div id="imageContainer" class="imageContainer"></div>

        <br/><br/>

        <h2>2. Passing data to callback functions (Static URLs used)</h2>
        <div id="imageContainer1" class="imageContainer horizontal"></div>
        <div id="imageContainer2" class="imageContainer horizontal"></div>
        <div id="imageContainer3" class="imageContainer horizontal"></div>
        <div id="imageContainer4" class="imageContainer horizontal"></div>
        <div id="imageContainer5" class="imageContainer horizontal"></div>
        
        <br/><br/>
        
        <h2>3. Static Example: Cross Browser Loading Spinner</h2>
        <div id="imageContainer" class="imageContainer">
            <img src="http://lorempixel.com/150/200/">
            <div class="crossBrowserLoadingSpinner">
                <div class="loadingSpinnerImage"></div>
            </div>
        </div>

        <script>
            (function (){
                "use strict";
                
                var nabisoft, btn, fBtnClickListener, fExample1, fExample2;
                
                //let's define an own namespace
                nabisoft = {
                    utils : {}
                };

                /**
                 * Utility function for detecting if an image could be loaded or not
                 *
                 * @author Nabi Zamani
                 * @copyright Nabi Zamani 2009
                 * @license Released under the MIT license
                 * @requires jQuery
                 * @param {String} opts.imgUrl       The url from where the image is to be loaded
                 * @param {Function} opts.beforeLoad A function to be called the loading is executed
                 * @param {Function} opts.complete   A function to be called when the loading has been completed
                 * @param {Function} opts.success    A function to be called when the image has been loaded successfully
                 * @param {Function} opts.error      A function to be called in case image could not be loaded
                 * @param {Object} opts.customData   A custom object that is passed to the event listeners 
                 * @see http://www.nabisoft.com/tutorials/javascript/loading-images-with-native-javascript-and-handling-of-events-for-showing-loading-spinners
                 */
                nabisoft.utils.loadImage = function (opts) {
                    var loadedImage = new Image();

                    if (typeof opts !== "object" || opts === null){
                        window.console && console.log("loadImage(): Please pass valid options");
                        return;
                    }

                    typeof opts.beforeLoad === 'function' && opts.beforeLoad({imgUrl:opts.imgUrl, customData:opts.customData});
                    loadedImage.onload = function(){
                        loadedImage.onload = null;
                        var oData = {success:true,url:opts.imgUrl,imageElem:loadedImage, customData:opts.customData};
                        typeof opts.complete === 'function' && opts.complete(oData);
                        typeof opts.success === 'function' && opts.success(oData);
                    };

                    loadedImage.onerror = function(){
                        loadedImage.onerror = null;
                        var oData = {success:false,url:opts.imgUrl,imageElem:loadedImage, customData:opts.customData};
                        typeof opts.complete === 'function' && opts.complete(oData);
                        typeof opts.error === 'function' && opts.error(oData);
                    };

                    loadedImage.src= opts.imgUrl;
                    if(loadedImage.complete){   //cached image
                        if (loadedImage.onload){
                            loadedImage.onload();   //we could also trigger it another way...
                        }
                        loadedImage.onload = null;
                        loadedImage.onerror = null;
                    }
                };
                
                //Now the Showcases:                
                fExample1 = function () {
                    var oOpts, imageUrlInput, imageContainer, loadingSpinner;

                    imageUrlInput  = document.getElementById("imageUrl");
                    imageContainer = document.getElementById("imageContainer");
                    
                    loadingSpinner = document.createElement('div');
                    loadingSpinner.className = "loadingSpinner";

                    oOpts = {
                        imgUrl     : imageUrlInput.value,
                        beforeLoad : function (oEvent){
                            imageContainer.appendChild(loadingSpinner);
                        },
                        complete : function (oEvent){
                            loadingSpinner.parentNode.removeChild(loadingSpinner);
                        },
                        success : function(oEvent){
                            imageContainer.innerHTML = "";
                            //we could also use this:
                            //while (imageContainer.hasChildNodes()){
                            //    imageContainer.removeChild(imageContainer.lastChild);
                            //}
                            imageContainer.appendChild(oEvent.imageElem);   //now place the image into the container
                        },
                        error   : function(oEvent){
                            imageContainer.innerHTML = "";
                            //we could also use this
                            //while (imageContainer.hasChildNodes()){
                            //    imageContainer.removeChild(imageContainer.lastChild);
                            //}
                            imageContainer.innerText = "ERROR";
                            //we could also use this
                            //imageContainer.appendChild(document.createTextNode("ERROR"));
                        }
                    };
                    nabisoft.utils.loadImage(oOpts);
                };
                
                fExample2 = function () {
                    var i, oOpts, tmpImageContainer, loadingSpinnerElement, 
                        fBeforeLoadingCallback, fCompleteCallback, fSuccessCallback, fErrorCallback;
                    
                    fBeforeLoadingCallback = function (oEvent) {
                        oEvent.customData.containerElement.appendChild(oEvent.customData.loadingSpinnerElement);
                    };

                    fCompleteCallback  = function (oEvent) {
                        var containerElem;
                        containerElem = oEvent.customData.containerElement;
                        containerElem.removeChild(oEvent.customData.loadingSpinnerElement);
                    };

                    fSuccessCallback  = function (oEvent) {
                        oEvent.customData.containerElement.innerHTML = "";
                        oEvent.customData.containerElement.appendChild(oEvent.imageElem);       //place the image somewhere
                    };
                    
                    fErrorCallback = function (oEvent) {
                        oEvent.customData.containerElement.innerHTML = "";
                        oEvent.customData.containerElement.innerText = "ERROR";
                    };

                    for (i=1; i <= 5; i++){
                        tmpImageContainer = document.getElementById("imageContainer"+i);
                        
                        loadingSpinnerElement = document.createElement('div');
                        loadingSpinnerElement.className = "loadingSpinner";
                        
                        oOpts = {
                            imgUrl     : "http://lorempixel.com/150/200/?v="+i,
                            beforeLoad : fBeforeLoadingCallback,
                            complete   : fCompleteCallback,
                            success    : fSuccessCallback,
                            error      : fErrorCallback,
                            customData : {
                                containerElement : tmpImageContainer,
                                loadingSpinnerElement : loadingSpinnerElement
                            }
                        };
                        nabisoft.utils.loadImage(oOpts);
                    }
                };
                
                btn = document.getElementById("loadImageBtn");
                fBtnClickListener = function () {
                    fExample1();    //execute example 1
                    fExample2();    //execute example 2
                };

                //we have to do this because only IE9+ supports addEventListener, but not IE8 and below :-(
                if (btn.addEventListener){
                    btn.addEventListener('click', fBtnClickListener);
                }else if (btn.attachEvent){
                    btn.attachEvent('onclick', fBtnClickListener);                  
                }

            })();
        </script>

    </body>

</html>

Have a look at the Demo to see this example in action. Hint: Only the image at the top is loaded from the url derived from the input field.

Comments