Blog

Taking Control of Image Loading

Tags: css3, fade-in, Image loading, javascript, jquery, lazy load, onload, responsive.

blog_imageloading_asset_1b

Image loading seems to be something that’s either overlooked entirely, or handed off to unnecessarily large plugins. Having a beautiful, smooth and speedy loading experience for your site is a crucial part of good UX, and should be considered a common courtesy to your designer. After all, who wants to see their design spoiled by choppy line-by-line image loading every time they log on?

Many of the sites I work on are photography heavy, and the benefits of high speed internet have been somewhat negated by the need to serve ultra-high resolution “retina” assets for a growing family of devices. There’s no better time to rein-in and take control of your image loading, and in this article I’ll demonstrate four lightweight techniques that will both make your site look great, and dramatically increase performance.

theagonyofdialup
Whenever I see ugly image loading, I’m reminded of this classic Comic Book Guy scene.

1. Single-Asset Image Loading

This is a technique you can apply to any and all images on your site to prevent (or rather hide) the traditional line-by-line loading of baseline JPGs.

We’ll start by wrapping every image in a div with the class “img_wrapper”:

<div class="img_wrapper">
	<img src="comicbookguy.jpg" alt=""/>
</div>

This wrapper will offer us some extra control over our image dimensions and aspect ratio that the img tag alone cannot provide. It also allows us to use a loading spinner – either as a background-image or a separate element (see codepen example) – that we can hide when the image is loaded.

For this example, we’ll constrain our image to a 4:3 aspect ratio – crucial for responsive content-managed sites. Note that we’ve also hidden our image with “opacity: 0;”, enabling us to control how and when we see it when the time comes.

.img_wrapper{
	position: relative;
	padding-top: 75%;
	overflow: hidden;
}

.img_wrapper img{
	position: absolute;
	top: 0;
	width: 100%;
	opacity: 0;
}

Every image element in the DOM fires a “load” event when all of its data has been downloaded from the server, and the image itself has been rendered by the browser. To capture and bind this event, we’ll need to use JavaScript.

I’m going to start by adding an “onload” attribute to the image tag.

<div>
	<img src="comicbookguy.jpg" alt="" onload="imgLoaded(this)"/>
</div>

For all of you youngsters that have never seen one of these, it’s called an inline script attribute, and allows us to bind JavaScript functionality directly to events triggered from DOM elements, much in the same way that we can add styling directly to elements using the inline “style” attribute. Believe it or not, these inline script attributes were a huge part of writing JavaScript in the early days of the web, and like inline styles, are generally frowned-upon today by semantics-nazis.

So for the rest of you who are about to run away in disgust at the sight of inline JavaScript, please stick around and take my word that this is still the single most efficient and bulletproof method of capturing the ‘load’ event of an image in the DOM. While I’m all for progress and HTML5 – I have absolutely nothing against using old-school techniques if they are still elegant and functional.

The alternative to this, would be to individually bind the load event to each image on “document ready”. The problem arises however, when images load before “document ready” fires, and before we have time to bind our functionality to each image’s load event. This is a particular issue when images are already cached by the browser from a previous session, and load instantly. We miss the event, and our function is never called. The “onload” attribute has none of these issues as it is “pre-bound” to the event, so to speak, and is therefore processed as the browser parses the HTML.

I have absolutely nothing against using old-school techniques if they are still elegant and functional.

With the onload attribute added, a function named “imgLoaded()” will be called the moment the image loads. This function must be placed in a javascript file in the <head> of your page (after jQuery if you are using it in your function, and after any other dependencies/plugins), so that it is defined before the <body> is parsed, and images are loaded. If we insert our function at the bottom of the page, it is highly likely that images will load before the function is defined.

With the “this” keyword, we are able to send the raw DOM object of the image to our JavaScript function as an argument:

function imgLoaded(img){
	var $img = $(img);

	$img.parent().addClass('loaded');
};

Or in plain JavaScript:

function imgLoaded(img){
	var imgWrapper = img.parentNode;

	imgWrapper.className += imgWrapper.className ? ' loaded' : 'loaded';
};

With our javascript, we are able to quickly traverse up the DOM one level, and add a “loaded” class to the containing wrapper element. I’m sure you’ll agree it’s an amazingly elegant solution. By selectively styling this class, we can now show our loaded image by setting its opacity to 1:

.img_wrapper.loaded img{
	opacity: 1;
}

To smooth out the process, we’ll add some CSS3 transitions to the img to achieve a “fading in” effect when our image loads.

.img_wrapper img{
	position: absolute;
	top: 0;
	width: 100%;
	opacity: 0;

	-webkit-transition: opacity 150ms;
	-moz-transition: opacity 150ms;
	-ms-transition: opacity 150ms;
	transition: opacity 150ms;
}

Check out a working example on codepen.io to see it in action, including an alternative version featuring a loading spinner.

Codepen.io Demo
Progressive JPGs

As a footnote to this technique, and in response to some of the feedback I’ve received on this article, it is definitely worth mentioning “progressive” JPGs. This is another throwback technique from the 1990s which involves saving JPGs as “progressive” rather than “baseline” to prevent line-by-line loading – instead presenting the user with a sequence of decreasingly pixelated versions of the image as it loads, all with the same height. The main benefit of this technique is that it prevents flowed content jumping around on the page as the images load and gain height.

Whether effects such as loading spinners and fade-ins are also distracting is a matter of personal taste, but at its core the wrapper div technique solves these issues with minimal CSS and JavaScript

The great thing about using the wrapper div technique however, is that we don’t have have to worry about images changing height as they load, nor do we have to submit our users to ugly pixelation, which for me, can be just as much a distraction for the user as baseline loading. It’s also worth nothing the process of redrawing the image several times actually puts additional strain on underpowered mobile devices. Whether effects such as loading spinners and fade-ins are also distracting is a matter of personal taste, but at its core the wrapper div technique solves these issues with minimal CSS and JavaScript, and without having to rely on the user (in a CMS situation) to save their JPGs in a certain way.

2. Grouped Multiple-Asset Image Loading

The above technique is all very well for individual images, but what if we have a collection of images to be displayed in a carousel or slideshow, or we’re using a layout plugin like Masonry? A common faux-pas when using carousel/slider plugins is instantiating them on “document ready”, often before all their images have loaded. This can cause the slideshow to transition to a blank, not-yet-loaded image, especially if we are dealing with hi-resolution photographs with large file sizes.

To prevent this, we need to instantiate our plugin of choice only when all the necessary images have loaded. Using a variation on the above technique, we will again add the “onload” attribute to all images in our slideshow:

NB: The markup below is meant only as a simplified approximation of the markup of a slideshow plugin, and should be adapted to your needs.

<div id="Slideshow">
	<img src="slide_1.jpg" alt="" onload="slideLoaded(this)" />
	<img src="slide_2.jpg" alt="" onload="slideLoaded(this)" />
	<img src="slide_3.jpg" alt="" onload="slideLoaded(this)" />
</div>

In our JavaScript, we will use the slideLoaded() function to track the progress of our image loading, and instantiate our plugin when ready:

function slideLoaded(img){
	var $img = $(img),
		$slideWrapper = $img.parent(),
		total = $slideWrapper.find('img').length,
		percentLoaded = null;

	$img.addClass('loaded');

	var loaded = $slideWrapper.find('.loaded').length;

	if(loaded == total){
		percentLoaded = 100;
		// INSTANTIATE PLUGIN
		$slideWrapper.easyFader();
	} else {
		// TRACK PROGRESS
		percentLoaded = loaded/total * 100;
	};
};

Each time an asset is loaded, we add the class “loaded” to it to track our progress.

With the final if statement, we instantiate our plugin (in this case jQuery EasyFader) when the number of images with the class “loaded” is equal to the total number of images in the container. As an added feature, we can divide the “loaded” variable by the “total” variable and use it to visualize the progress for the user either by displaying the percentage, or by using it to control the width of a progress bar or similar.

Again, this script must be placed in the <head> of your document, after jQuery and whatever plugin you’ll be instantiating when ready.

3. Pre-caching Images for Performance

On image-heavy sites, we can alleviate some of the strain of image loading by silently loading images into the browser’s cache in the background while the user is idling on a page.

For example, let’s say I have a multi-page site where each secondary page has a hi-res full-width “hero image” across the top. Rather than forcing the user to endure the loading time of these images every time they hit an individual page, we can load them into the cache before the user gets to the page. Let’s start by putting their URLs into an array:

<script>
	var heroArray = [
		'/uploads/hero_about.jpg',
		'/uploads/hero_history.jpg',
		'/uploads/hero_contact.jpg',
		'/uploads/hero_services.jpg'
	]
</script>

I will normally use my CMS or whatever backend I’m building on to pass this data onto the page itself in the form of a script tag in the footer. This way, the list of images can be dynamically updated and expanded.

When the user arrives at the home page of my site, I will wait until the homepage has loaded in its entirety before doing anything to make sure I don’t interrupt the loading of actual page content by adding unnecessary overhead. To do this, I will attach my JavaScript functionality to the “window load” event, which fires only when all content for the page has been downloaded and rendered (including images), unlike “document ready” which fires as soon as the DOM is ready:

function preCacheHeros(){
	$.each(heroArray, function(){
		var img = new Image();
		img.src = this;
	});
};

$(window).load(function(){
	preCacheHeros();
});

Or in plain JavaScript:

function preCacheHeros(){
	for(i = 0; i < heroArray.length; i++){
		var url = heroArray[i],
			img = new Image();

		img.src = url;
	};
};

window.onload = preCacheHeros();

Using a loop, we iterate through our heroArray array, creating an empty image object for each iteration and then setting its source as the URL of our hero image. Doing this loads the image into the browser’s cache for the current session, so that when the user does visit the page featuring the image, it will display instantly.

It’s worth nothing that while the practice of pre-caching will speed up loading and improve UX on the client-side, it will actually increase strain on the server. For this reason, it’s worth taking a look at your site’s analytics before implementing pre-caching. If the majority of your users hit your site’s homepage and leave before visiting secondary pages, the cost of the extra requests on your server may outweigh the benefits afforded to the few users that stay and and take a look around.

It’s worth taking a look at your site’s analytics before implementing pre-caching

Pre-Cognitive Pre-Caching
blog_imageloading_asset_2a

With that said, if you do want to implement pre-caching, I recommend splitting up the images to be pre-cached into meaningful groups, positioned strategically around the site. For example, knowing that the vast majority of users who stay on the site will navigate to a secondary page after visiting the homepage, I would pre-cache secondary page hero images while on the homepage.

Let’s say however that each of my blog posts also has a hi-res hero image. Pre-caching these while on the homepage would not only put a huge stress on the server, but may be a waste of resources, as let’s say only 15% of users arriving on the homepage navigate to a blog post. The best place to pre-cache the blog hero images would be on the blog landing page, knowing that nearly all users will navigate to a blog post from there.

This is what’s known as pre-cognitive pre-caching, where we use statistical data from our analytics to predict behavioral patterns in how our users navigate through the site. It may sound like something from science-fiction, but you’d be surprised at how accurately we can predict user flow and turn it to our (and the user’s) advantage.

It may sound like something from science-fiction, but you’d be surprised at how accurately we can predict user flow

4. Lazy-Loading Images to Throttle Server Stress

The term “lazy-loading” refers to the practice of programmatically loading images after a specific event, to prevent the browser requesting and rendering all images on a page at once as the document is parsed and rendered.

Common uses are long or image-heavy pages in general. On the Barrel blog landing, we combine lazy-loading with the MixItUp plugin to ensure that images not in the current filter or page are not loaded unnecessarily, until that element is visible.

For any images that we want to lazy-load, we’ll again wrap them in a div with the class “img_wrapper” which we’ll also give the class “lazy_load” so we can target them easily with jQuery:

<div class="img_wrapper lazy_load">
	<img
		src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
		data-src="comicbookguy.jpg"
		alt="Comic Book Guy"
	/>
</div>

Notice that the url of the image is referenced only in the “data-src” attribute of the img, and not the “src”. This prevents the browser from loading the image on first pass of the document. Instead we assign a 1 x 1px .gif (and I bet you thought you’d seen the last of those too!) to the “src” attribute, in the form of a base64 string. This has the advantage of being valid and preventing the request on the server caused by referencing a physical file of the .gif.

To lazy-load our image, we simply need to grab the “data-src” value, and assign it to the “src”. Doing this will fire the ‘load’ event, so we will also want to bind that event just before we assign the new src, so that we can use the first technique to fade-in the image:

function lazyLoad(){
	var $images = $('.lazy_load');

	$images.each(function(){
		var $img = $(this),
			src = $img.attr('data-src');

		$img
			.on('load',imgLoaded($img[0]))
			.attr('src',src);
	});
};

$(window).load(function(){
	lazyLoad();
};

The above function lazy-loads all images after the window load event fires, but the code within the .each() loop can be adapted to suit a variety of situations. A very common use would be to attach it to the “window scroll” event, and lazy load images whenever they scroll into the viewport.

Go Forth and Image Load

While I had been experimenting with a couple of these techniques on various projects over the last 12 months or so, I was forced to really boil them down and refine them for use on the recently re-designed barrelny.com (launched back in April) where I used a combination of all four to provide a graceful image loading experience while trying to squeeze every ounce of performance out of an extremely photo and image heavy website. By combining things like pre-caching and lazy-loading with AJAX page loading, slideshows and client-side pagination we were able to create a smooth and seamless user experience throughout the site.

While trying to distill these techniques down for a Barrel dev-team presentation, I was pleasantly surprised both with how lightweight all of these techniques are on paper – typically 5-10 lines of code in jQuery – and how easy they are to integrate into any project. All of them could also be written in plain JavaScript without too much trouble and extra code, but if you using jQuery, as we often do, its bulletproof DOM traversal techniques should definitely be taken advantage of.

These techniques are by no means the only way to accomplish their respective functionalities, but can all be easily adapted to suit existing frameworks and plugins. If you haven’t already been thinking about image loading, I hope you are now! Why not integrate one or two of them into your next project?

Illustrations by Lucas Ballasy

Popular This Week
Cult Fitness Vol. 2: Customer On-boarding & Continued Engagement through Email
May 19, 2015

Cult Fitness Vol. 2: Customer On-boarding & Continued Engagement through Email

By Diane Wang
5 Blind Spots to Consider When Designing Web Apps
July 31, 2014

5 Blind Spots to Consider When Designing Web Apps

By Yvonne Weng
25 Must-Have Pages for Your E-commerce Website
January 30, 2015

25 Must-Have Pages for Your E-commerce Website

By Yvonne Weng
New Shopify Theme: Mosaic
January 16, 2016

New Shopify Theme: Mosaic

By Peter Kang

Like what you’re reading? Sign up for the Barrel newsletter and receive updates.