Optimizing your webpage — Image Loading

This article is part of my series on how to optimize a webpage for 10k size.

Loading images async is done pretty easy and pretty easy done wrong.

To load all images on this blog async, I just renamed the src and srcset attributes to data-src and data-srcset and add a class ll (lazy-loading) to it. In JavaScript I just search for all elements with the ll class, rename the attributes back to src and srcset, which will trigger the browser to load the images, and remove the ll class. You can view the script on GitHub.

Inserting this in a page that has several images in the text, you have just created the worst user experience possible - jumping content. See this post to read why jumping content is such a bad usabilty. In short: you interrupt the user’s interaction flow with your page.

Before the images have been started loading there is just an 0 height img tag in the page. As soon as the browser has loaded the first bytes of the image, that contain the dimensions of the image, it will resize the img tag to this dimensions, giving it a new height. That way all the content below the img tag suddenly jumps.

To be honest: that behaviour will also happen with regular img tags without lazy loading before the browser has these first bytes. But since the browser immediately start loading the image while parsing HTML, you will require a very slow connection to witness that behaviour. With async loaded images it is very likely you will see the jumping content also on fast connections.

To get rid of this jumping content (also for regular loaded imgs) you need to know the size of the image on the server when building the HTML. You could than set the width and height of this image to the img element (or a wrapper around it) so that the element already has the correct dimensions before the information from the actual image is there, e.g. instead of placing a <img src='foo.png'> into your HTML you would use <img src='foo.png' width="300" height="200">.

That solution unfortunately doesn’t work well if you have something like max-width: 100% in your CSS for small screens. In that case the image would still have the full height on small screens, because the height value doesn’t scale down when the width scales down, due to the max-width limit. You would need to set something like: max-width: 100%; height: 1.6*width where 1.6 is the aspect ratio of the image to get the correct height no matter what the actual width will be.

Unfortunately there is no possiblity in today’s CSS to set this easily. Luckily there is a CSS workaround. The padding values of an element are - when specified in percentage - in percents of the width of the element. I use this on my blog to create a container around every image, that looks as follows:

<div class="image-wrapper">
  <div class="image-placeholder" style="padding-bottom: 62%">
  <img src="foo.png">
</div>

With 62% being is the aspect ratio of the image (height / width * 100). I use a build system to generate static HTML output of that form, but you could also generate it on the fly with PHP, Node or something else. You can find the HTML generation for images in the Renderer.js of my blog.

The image-placeholder element now have the correct height in relation to the width of the image-wrapper. It will prevent any jumping content, since you now have the correct height as soon as the HTML loaded, before any byte of the actual image is transfered. To position the img and image-placeholder above each other, just position the img absolute and give the image-wrapper class a position: relative.

You can also use the image-wrapper element to add some placeholders before the image is loaded, e.g. via an inline SVG in the background of that class. This CSS should be included in the above-the-fold CSS to make sure the placeholder is visible from the beginning. You can check the CSS for my images on GitHub.