Blog

Taking Control of Image Loading

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

July 29, 2013 by
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 reign-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

  • http://www.japborst.net/ Jelmer Borst

    Nice overview of techniques and very useful too. However, I’m wondering: why not just use progressive JPGs and save yourself from loading jQuery and other scripts along with it.

    • barrelny

      Good question. Most of the sites we build leverage jQuery, so it’s usually the first script to load. If it’s there, we may as well take advantage of it’s methods. Progressive JPGs are definitely valid too, but I personally don’t like the look of them during loading, and would prefer to wait for the finished image. It’s really down to personal preference I guess!

  • SairamKunala

    How does setting src to not the default image impact SEO ?

    • barrelny

      It definitely doesn’t help SEO as search engines will most likely ignore the images – it’s a technique that should be used only when performance is paramount, and should be applied strategically. Having said that (and I’m by no means an SEO expert), if you follow follow best-practices in your markup, and your content is legit, loosing some images here and there shouldn’t be a big loss.

    • http://www.andreapernici.com Andrea Pernici

      You can use a mixed sitemap URL+Image to be SE Compliant.
      The spider has no problem in finding the image even if is in other attributes.
      Tested as any good SEO should do.

  • bzle

    Thanks for sharing those techniques. Controlling image loading has always been a bit intimidating, but after reading your tutorials… I think I’ll give it a shot. Really like the new barrelny.com btw

  • Freddywang

    Note for lazy loading technique, don’t use it for iOS. Apparently on iOS device, during the inertia scrolling event (the momentum scroll after you release your finger), it doesn’t page offset at all. Since lazy load determine which image to load based on scroll offset you will get momentary blank spot till momentum scrolling stops. I still experience this issue at least until iOS6. Maybe it will be fixed on next release.

    • barrelny

      That’s definitely worth nothing, but you’re referring specifically to lazy loading attached to the scroll event – iOS currently pauses all JavaScript during scroll to divert CPU power to rendering the page. The technique in this article deals with the lazy loading itself (i.e. programatic image loading), not the event that triggers it, which could be any number of different things.

      • Freddywang

        You are right. The technique above is trying to throttle image loading, that’s definitely different from what I thought. I have seen many lazy loading that try to optimize based on scroll position. Unfortunately those don’t work perfectly on iOS due to the fact above.

        Scroll event is actually triggered. But reported offset is not properly updated. The only time javascript stopped is when it reach the edge of page, creating the pull effect bounce back. During that period, all javascript activities are completely exterminated.

        • Dominic Whittle

          Using the .img_wrapper technique (which sits behind the img, preserves space in the layout, and is visible until the img is faded in) combined with lazy loading you can set an .img_wrapper background image (something like the Disqus “add image” icon) to imply an image will be added.

          Works for me.

          • barrelny

            Yeah that’s great. I’ve ended up using that technique quite a few times too. I have a variation of the image wrapper technique built for background images, where it sets an inline style of background once the image is loaded. That also gives you even more control over aspect ration/cropping etc with all the background positioning properties.

          • Dominic Whittle

            Oh nice! Background sizing has pretty good support (IE9+) so that’s probably not a bad solution for responsive images.

  • i_like_robots

    Relating to the first technique I took it a step or two further: http://maketea.co.uk/2013/05/04/responsive-image-placeholders.html

    I think the performance benefit of avoiding recalculating layout is well worth the little effort it takes to implement.

  • Arden de Raaij
    • barrelny

      It’s really a question of wether you want to leverage a plugin, or write a couple of lines of code and manage it yourself. This article is intended help developers understand how image loading works, and how easy it is to integrate some techniques, without having to resort to YAJP (yet another jQuery plugin!)

      • Arden de Raaij

        Thanks for your reply. I understand, I was just thinking that sharing some already available alternatives would complete the article.

  • Bobby Kenner

    Great read. We found that loading a lower quality image (high quality in viewport) on page load and lazy loading high quality images worked very well for us. This method turned out to be a win/win for our plt/seo woes.

    • barrelny

      Ah yes great solution for SEO – there was another question below regarding this. Thanks!

  • Pingback: Taking Control of Image Loading | devJerk

  • Pingback: Taking Control of Image Loading | BARREL |...

  • http://www.superpencil.com/ Pascal Verstegen

    I remember doing this for my portfolio site a few years ago, and in the revision it didn’t even cross my mind.. thanks!

  • msodrew

    Thanks for the straightforward write up & tips.

    My minor 2c is that all your CSS classes that employ javascript (such as .lazy_load) should be namespace prefixed to avoid confusion with CSS classes that otherwise offer only style rules.

    So change .lazy_load ==> .js-lazy-load

    • barrelny

      It’s only intended as an easy to understand example to demonstrate the technique! Feel free to do ever you want with the class names.

      • msodrew

        ah, quite right… my head seems to have wandered somewhere else at time of writing

  • http://ecmazing.com/ Šime Vidas

    In Firefox, your code blocks have an useless vertical scroll bar which looks ugly. I’ve removed overflow:auto !important, and added overflow-x:auto; overflow-y:hidden;. This will fix the scroll bar issue in Firefox.

    • barrelny

      What OS are you on? Everything looks fine in Chrome and FF on Mountain Lion.

      You have convinced me to sign up for a twitter account ;) https://twitter.com/PatrickKunka

      • http://ecmazing.com/ Šime Vidas

        Windows 7

  • acrookston

    Nice write up. I used the later technique to load a tumblr-like image feed with lots of higher resolution images in a mobile app (which had a web-based feed). It was basically lazy-loading/unloading depending on the location of the image compared to current view port of the device. It actually made it possible for us to use an infinite scroll on devices with very low capabilities, like early gen ipod touches.

  • amboy00

    The next time someone tells me to just use a jQuery plugin, I’m sending them here.

  • Pingback: Image Loading | Web Code

  • Pingback: Design Focus: Assembly Line | Devlounge

  • qgustavor

    I’m the troll guy that will press ESC on your website, then “View Page Source” and open all your images in tabs.
    I like old school loading: if I see a placeholder instead the image I think that the connection failed and the plugin is not listening to the ‘onerror’ event. I don’t have to know if the plugin handles it when I’m visiting a website.

  • petewarman

    Some nice ideas but I just wanted to pick up on one thing

    “By using jQuery, we are able to quickly traverse up the DOM one level, and add a “loaded” class to the containing wrapper element.”

    Is this not equally as easy without jQuery? something like:
    var imgWrapper = img.parentNode;
    imgWrapper.className += imgWrapper.className ? ‘ loaded’ : ‘loaded’;

    • barrelny

      Absolutely! Perfect example of how this could be done in pure JS with very little extra code. I should probably add that as an alternative. I still want to show the lineage between technique 1 and 2 though (which definitely benefits more from jQuery), so I think it would be good to see both. Thanks!

  • Pingback: Weekly Design News – Resources, Tutorials and Freebies (N.196)

  • Pingback: Weekly Design News – Resources, Tutorials and Freebies (N.196) « Refined Sites Blog

  • Pingback: Reactive Programming, CSS Spacing, & Image Loading - Treehouse Show Episode 52 - Treehouse Blog

  • jaystrab

    You know what’s really annoying, is when I see loading graphics every time I click a link to view a new page. Oh, wait… hmm.

  • Pingback: Weekly Design News – Resources, Tutorials and Freebies (N.196) | Sophisticated Design News

  • Pingback: Reactive Programming, CSS Spacing, & Image Loading – Treehouse Show Episode 52 | Html5 Tutorials

  • Pingback: Design Resource Tutorials and Freebies | CSTLdesign

  • Pingback: Tomando el control de la carga de imágenes | Blog de unique3w

  • Pingback: Image onload seems to be called early when I pass in image | user43

  • Pingback: Internet Hoarding #4

  • Pingback: My Very Best Ping

  • Pingback: best universities

  • Pingback: online colleges

  • Pingback: http://vimeo.com/88321430

  • Pingback: https://twitter.com/1OnlineColleges

  • Pingback: www.youtube.com

  • Pingback: colleges in san diego

  • Pingback: criminal justice degree

  • Pingback: cna nursing schools

  • Pingback: download itunes

  • Pingback: http://www.i-newswire.com/

  • Pingback: cash advance

  • Pingback: vb6 source codes 2014

  • Pingback: http://www.youtube.com/watch?v=IeAlhDMnF_U

  • Pingback: cheap laptop utilities

  • Pingback: Payday Loan Places On Stony Plain Road

  • Pingback: Payday Loans Oshawa Ontario

  • Pingback: Payday Loan For Odsp

  • Pingback: Online Payday Loans

  • Pingback: Payday Loans Toronto

  • Pingback: Payday Loans In Edmonton

  • Pingback: payday loans direct lender instant approval

  • Pingback: seo backlinks 2014 forum

  • Pingback: super hosting

  • Pingback: cna classes

  • Pingback: ps3 downgrade blackpool

  • Pingback: Greater Manchester Social

  • Pingback: title loans

  • Pingback: youtube.com

  • Pingback: loan max locations

Popular This Week
Building a jQuery Slideshow Plugin from Scratch
May 05, 2013

Building a jQuery Slideshow Plugin from Scratch

By Patrick Kunka
Text-align: Justify and RWD
March 12, 2013

Text-align: Justify and RWD

By Patrick Kunka
Taking Control of Image Loading
July 29, 2013

Taking Control of Image Loading

By Patrick Kunka
How We Use Template Tables to Effectively Communicate UX Designs
February 11, 2014

How We Use Template Tables to Effectively Communicate UX Designs

By Yvonne Weng

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