According to the HTTPArchive site, as of 2015, images accounted for 737 KB of 1180 KB of average page size – about 63 percent. Even if developers jump through hoops to optimize and minify JavaScript and CSS to make them as small as possible, those actions can never have the same kind of impact of reducing image sizes. Image optimization goes a long way in that regards, as does lazy image loading, using JavaScript. In a nutshell, lazy loading consists of only displaying visible images on page load, while delaying the loading of other images until such a time that they are needed. One application where lazy loading’s potential is immediately apparent is in product lists. Since most lists may display anywhere from 60 to 120 or more items, it makes sense to wait until a product is about to come into view before loading it. In today’s tutorial, we’ll get a feel for what’s involved in doing that; in follow-up installments, we’ll learn how to add an offset to load images earlier as well as how to use some of the outstanding lazy loading libraries.
Loading an Image Dynamically
Creating an image using JavaScript offers the performance advantage of not downloading the image until the src property is set. Until then, the image does not appear in the document.
var image = new Image(); image.onload = function(){ document.body.appendChild(image); }; //all kinds of stuff could happen here... image.src = 'myimage.jpg';
So the trick is how do you set an image’s source without actually setting the src property. There are many solutions to this problem. One particularly interesting one is HTML5’s specialdata-prefixed attributes. Their purpose is specifically to store extra information on standard HTML elements without resorting to hacks such as non-standard attributes, extra properties on the DOM, etc… Hence, we can assign the image path to an attribute named “data-src”. Then, when we’re ready to load the image, we can copy its value to the src.
Here is a group of DIVs that we will be using for the demo. Each one contains a different image from my own collection:
<div class="container"> <div> <img data-src="http://robgravelle.com/@/Storage/_files/99/file.jpg" /> </div> <div> <img data-src="http://76.12.22.162/images/bank/SS_v2_vintage.jpg" /> </div> <div> <img data-src="http://robgravelle.com/@/Photos/_entries/26/photo-full.jpg" /> </div> <div> <img data-src="http://robgravelle.com/@/Photos/_entries/28/photo-full.jpg" /> </div> <div> <img data-src="http://robgravelle.com/@/Photos/_entries/11/photo-full.jpg" /> </div> <div> <img data-src="http://robgravelle.com/@/Photos/_entries/12/photo-full.jpg" /> </div> <div> <img data-src="http://robgravelle.com/@/Photos/_entries/10/photo-full.jpg" /> </div> <div> <img data-src="http://s3.amazonaws.com/content.sitezoogle.com/u/107988/124d7075bd224fceaecf8172ca200f088653378f/large/IKnew-nologo.jpg" /> </div> </div>
Binding Handlers to Browser Events
Our image loading code will have to know when it’s time to set the image src. There are three possibilities:
- When the page first loads.
- When the browser window is resized.
- When the user scrolls down the page.
My script is jQuery-enhanced because it makes adding and removing event handlers much easier to do, and also eliminates the need to write cross-browser code. I would suggest that you always use a JS library with all but the most trivial of scripts. Case in point, we can bind the same handler to a number of events simply by supplying a list of space-separated event names. In addition to the three events above, we are also binding to the DOMContentLoaded event. Unlike the load event, which fires only after stylesheets, images, and iframe content have finished loading, the DOMContentLoaded event is fired when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and iframe content to finish loading. People often make the mistake of using load where DOMContentLoaded would be much more appropriate.
We are using the DOMContentLoaded event here to fetch the current vertical position of the scroll bar as soon as the main document content is loaded. The current scroll bar position is stored in the local lastScrollTop variable so that it may be compared to the latest scroll position in the event handler function. The latter is short circuited if the page is being scrolled upwards rather than downwards. The lastScrollTop is then updated at the end of the handler function on scroll events.
var lastScrollTop = 0, eventList = 'DOMContentLoaded load resize scroll', $window = $(window); $window.on(eventList, function (evt) { var scroll = $window.scrollTop(); if (evt.type == 'DOMContentLoaded') { lastScrollTop = $window.scrollTop(); return; } else if (evt.type == 'scroll' && scroll <= lastScrollTop) { return; //exit function if not scrolling down. } //code to display DIVs will go here //store the scroll bar's latest lastScrollTop evt.type == 'scroll' && (lastScrollTop = scroll); });
Notice that the last line of the handler function employs a slightly shorter idiomatic version of the if statement. Due to short circuiting, the statement after the double ampersand (&&) doesn’t evaluate unless the preceding expression is truthy.
Conclusion
I the next installment we’ll write the code to display the DIVs. Once we’ve got the demo up-and-running, we’ll add an offset to load the images a little sooner.
Rob Gravelle resides in Ottawa, Canada, and is the founder of GravelleWebDesign.com. Rob has built systems for Intelligence-related organizations such as Canada Border Services, CSIS as well as for numerous commercial businesses.
In his spare time, Rob has become an accomplished guitar player, and has released several CDs. His band, Ivory Knight, was rated as one of Canada’s top hard rock and metal groups by Brave Words magazine (issue #92) and reached the #1 spot in the National Heavy Metal charts on Reverb Nation.