Lazy loading responsive images

I recently presented a workshop at Fluent conference and in preparing for the session I was looking for examples of lazy loading responsive images, mainly an image that was displayed using the <picture> element.  If you are anything like me when programming and write one line of code followed by a line sourced from Stack Overflow then you will be as surprised (and disappointed) as me not to find anything.

I did find many articles advising just to use the library lazysizes or similar libraries, but there is always something that doesn’t sit right with me about blindly adding libraries to your website.

When thinking about the problem now, my guess is that people don’t bother lazy loading a responsive image. This could be because when using JavaScript to load the image you can also use JavaScript to determine the size of the required image too, combining the 2 operations into one.  However, there is one important part in optimal image loading that isn’t easy to do in JavaScript that should not be ignored.

Using a <picture> element to switch image formats can provide huge savings, especially if you are using a PNG with transparency, converting this to webP can often reduce the bytes by more than half as well us providing the chance to apply lossy compression to save even more bytes.  While you can use JavaScript to determine the browser, who wants to maintain logic for browser support in their code? (certainly not this lazy developer!)

So with no help from Stack Overflow (and other Google searches) and a desire NOT to use a library below is a simple enough solution I came up with.

Let’s assume you want to create a simple <picture> that swaps out 2 different size images (400 & 800) based on screen size and also swaps the format to webP when supported in the browser.  The images are already created with the same filenames, but exist in a folder structure for sizes.

<picture id="lazyPicture">
     <source type="image/webp"
          srcset="/400/hero.webp 400w, 
          /800/hero.webp 800w"  
          sizes="100vw" />
     <img src="/800/hero.jpg" 
          srcset="/400/hero.jpg 400w, 
          /800/hero.jpg 800w" 
          sizes="100vw" />
</picture >

In order for the browser not to load this image on first pageload we need to strip it down and remove any src or srcset attributes.  In addition to these we also need to add a data-src attribute to hold the filename.  Note this does NOT include the file extension.

<picture id="lazyPicture" data-src="hero">
</picture >

Once the markup has been changed we need to build the function to get the data-src value and swap this into the <source> element and the <img> element.

var x = document.getElementByID("lazyPictures");
var fileName = x.getAttribute("data-src");

/* create & append the source element */
var src = document.createElement("source");
src.type = "image/webp";
src.srcset = "/400/"+fileName+".webp 400w, 
          /800/"+fileName+".webp 800w";
src.sizes = "100vw";

/* create & append img element */
var img = document.createElement("img");
img.src = "/800/"+filename+".jpg";
img.srcset = "/400/"+fileName+".jpg 400w, 
          /800/"+fileName+".jpg 800w";
img.sizes = "100vw";

The code above finds our <picture> by using the id attribute.  Then it finds the filename for the image held in the data-src attribute.  Once it has this we can create additional elements for <source> and <img> and finally append these to our <picture>.  We now have a fully functioning responsive image that can be lazy loaded.

I have found this to be extremely useful on large hero or carousel images, especially when the page is loaded with http2.  Currently browser and server communications to deal with prioritising resources is pretty limited and as a result, large hidden hero images use precious bandwidth that more critical resources need.  There are a couple of proposals out there to solve this issue, the first addressing the issue with http2 is priority hints and the second is allowing the browser to control lazy loading of images. This seems most sensible since we can already defer loading of JavaScript, why cant we do it with images?  Until either of those get ratified we are stuck using JavaScript to hack our performance wins similar to what is above.