Progress (0%)

# Multiple Images

Chapter 9 8 mins

Learning outcomes:

## Introduction

Uptil now we've covered a lot of key ideas and concepts revolving around lazy loading but in terms of a single image. All the examples and code snippets we saw in the previous chapter were curated for a single image. However nearly none real word example needs to lazy load just one image - the numbers are almosy always at least higher than one.

Likewise in this chapter we will see how to curate our algorithm to lazy load multiple images based on the concepts learnt for a single one.

So let's begin on with a journey of restructuring.

## Making everything multiply

In this chapter we will consider three images to be lazily loaded.

And now it's time to put your thought into a test. For all the programmers out there do you think this loading multiple images would be any difficult? What tools will you need to build this algorithm? How will you need to restructure the previous JavaScript for a single image.

Listing out the tools we would first need a loop to iterate over all the images and as we do so carry out the respective offset calculation for each image and assign a scroll listener for it.

This can be done easily using a function closure. If you don't know what a closure is, consider reading this amazing explanation on the tough concept of JavaScript closures.

Since now we are in the hood of multiple images we rename our previous variables `lazyCont` and `lazyImage` to `lazyConts` and `lazyImages` respectively; to sound plural.

``````var lazyConts = document.getElementsByClassName("lazy");
var lazyImages = document.getElementsByClassName("lazy_img");
var len = lazyConts.length; // number of lazy images

for (var i = 0; i < len; i++) {
(function(i) { // the closure
// calculate offset for the image
var offset = lazyConts[i].getBoundingClientRect().top + window.pageYOffset - window.innerHeight;

// add a new scroll listener to listen for this offset
if (window.pageYOffset > offset) {
lazyImages[i].src = lazyImages[i].dataset.src;
}
});
})(i)
}``````
Don't get panicked by looking at the complexity of this code - it's exactly the same code that we constructed in the previous chapters with just slight changes to deal with multiple images.

Let's explain some parts of this code:

Wherever, previously, we had `lazyCont` or `lazyImage` written now we have `lazyConts[i]` and `lazyImages[i]` to select the ith index image.
Instead of the `onscroll` property here we've used `addEventListener()` to add a scroll event to `window` because this time we need to add another scroll listener with each subsequent image, not just one scroll listener.
The closure (from line 6 - 16), would serve to encapsulate the all our code for the algorithm dealing with multiple images. It remembers the variable `i` passed to it and thus can operate and resolve things using it easily.

Without this closure we would've been in a complete state of mess; it would've then been impossible to load multiple images!

Next we need to give a loading icon to every lazy image just like we gave one to a single image in the Loading Icons chapter and the class `.unloaded` to give it a fade-in transition when it loads into view, as we did in the Fade Effect chapter. The CSS remains the same as before.

``````for (var i = 0; i < len; i++) {
(function(i) {
var loader = document.createElement("span");
loader.innerHTML = '<div class="icon"><span></span><span></span><span></span></div>';

// add class .unloaded for the fade effect to work

var offset = lazyConts[i].getBoundingClientRect().top + window.pageYOffset - window.innerHeight;
if (window.pageYOffset > offset) {
lazyImages[i].src = lazyImages[i].dataset.src;
}
});
})(i)
}``````

Now onto the load and error events for our lazy images - same as before except for only changing to the new variables `lazyImages` and `lazyConts`.

``````for (var i = 0; i < len; i++) {
(function(i) {

loader.innerHTML = '<div class="icon"><span></span><span></span><span></span></div>';
lazyImage.src = lazyImage.dataset.src;
}

lazyImages[i].onerror = function (e) {
}

lazyImages[i].onload = function () {
lazyImages[i].onerror = null; // remove error handler
}

/* code to calculate offset and assign scroll listener */
})(i)
}``````

If a lazy image loads without any error this code works perfectly without any error. However if the image fails for some reason, this code throws an error in the console: `"Undefined function reloadImage()"`. Why does this happen?

Recall from the previous chapter that when an image fails to load, the underlying `onerror` event is fired - and indeed it does fire in this algorithm as well. The problem is that the moment we click the reload button we call a function namely `reloadImage()` that is just not defined globally.

If you know about variable scoping; resolving variable values; event calls and current scope resolution then you will right away be able to pick up the reason to why is `reloadImage()` considered NOT to be defined.

Well even if you don't know these concepts well, here is the intuitive explanation.

Any variable declared inside a function is available ONLY inside that function. In our case since we have specified the click event listener for the `.icon` element inside the `onclick` attribute rather than the `onclick` property, the interpreter tries to find a global function namely `reloadImage()`.

Since it cant find any function with the name `reloadImage` once we click the reload icon, it throws an error in the console.

This can be rectified in two ways:

1. By declaring `reloadImage()` globally and altering it slightly to work from the global environment
2. By shifting the click event of `.icon` from the onclick attribute to the onclick property

The latter is relatively quicker as compared to the former which requires more thinking on your side. Hence we will go with the latter approach. You should however try out the former as well, as it would be a good exercise for you to do.

``````for (var i = 0; i < len; i++) {
(function(i) {

loader.innerHTML = '<div class="icon"><span></span><span></span><span></span></div>';
lazyImage.src = lazyImage.dataset.src;
}

lazyImages[i].onerror = function() {
loader.innerHTML = '<div class="icon reloader"><i class="fas fa-redo"></i></div>';
}

/* code for the onload event */
/* code to calculate offset and assign scroll listener */
})(i)
}``````

First we give the reloading icon another class - `.reloader` - to be able to select it easily. The variable `reloader` serves to select this very element for a failed image which has fired the `onerror` event. After selecting the element, finally in line 14 we assign it a click event with the handler `reloadImage`.

Now we've gotten things to work smoothly and flawlessly!

The last thing left to do is to remove the scroll listener for each image once it loads into view.

Since we've used `addEventListener()` to add a scroll listener for each image, we'll need to use the `removeEventListener()` method to remove it.

The problem with the method is that it needs a function name to remove listening for a given event - we can't mention a function definition directly and get the handler removed!

So in solving this problem we create another variable `lazyFx` holding the function definition of our scroll handler and then accordingly use it in the `addEventListener()` and `removeEventListener()` methods to add and remove the handler respectively from the scroll event.

``````for (var i = 0; i < len; i++) {
(function(i) {
/* code for the reloadImage() function and onerror event */

/* code for the onload event */

var lazyFx = function() {
if (window.pageYOffset > offset) {
lazyImages[i].src = lazyImages[i].dataset.src;
window.removeEventListener("scroll", lazyFx);
lazyFx = null;
}
}
In line 12 we write `lazyFx = null` just optionally to empty the variable `lazyFx` when its corresponding image comes into view.