David Walsh Blog

Simple Image Lazy Load and Fade

One of the quickest and easiest website performance optimizations is decreasing image loading.  That means a variety of things, including minifying images with tools like ImageOptim and TinyPNG, using data URIs and sprites, and lazy loading images.  It’s a bit jarring when you’re lazy loading images and they just appear out of nowhere which is why I love the fading in route.  The page still shuffles if you aren’t explicitly setting image dimensions but the fade in does provide a tiny bit of class.  I’ve seen many solutions which accomplish this (some not very good, like my old method) so I thought I’d share my current implementation.

The HTML

We’ll start by putting together the image tag with specifics:


<img data-src="/path/to/image.jpg" alt="">

Use data-src to represent the eventual URL.

The CSS

Any image with a data-src attribute should start as invisible and eventually transition the opacity:


img {
	opacity: 1;
	transition: opacity 0.3s;
}

img[data-src] {
	opacity: 0;
}

You can probably guess at this point what we’ll be doing with that attribute when an image loads…

The JavaScript

…which is removing the data-src attribute when the image has loaded:


[].forEach.call(document.querySelectorAll('img[data-src]'), function(img) {
	img.setAttribute('src', img.getAttribute('data-src'));
	img.onload = function() {
		img.removeAttribute('data-src');
	};
});

This solution does require JavaScript as a few of you have pointed out. For a fallback solution you could do this:


<noscript data-src="/path/to/image.jpg">
<img src="/path/to/image.jpg" data-src="" alt="">
</noscript>

[].forEach.call(document.querySelectorAll('noscript'), function(noscript) {
	var img = new Image();
	img.setAttribute('data-src', '');
	img.parentNode.insertBefore(img, noscript);
	img.onload = function() {
		img.removeAttribute('data-src');
	};
	img.src = noscript.getAttribute('data-src');
});

This is a super basic tutorial but considering I’ve seen so many other solutions, I thought I’d share what I’ve implemented;  it works under every scenario I’ve tested, including History changes via AJAX (like my site does).

Of course this doesn’t take into account true scroll-based lazy load but that’s generally done by a plugin in your favorite JavaScript framework or a standalone component. If you’re looking for a simple solution, however, this is it!