Placeholder Images

The size of my images changes fluidly with my responsive layout. Since the browser does not know their heights a priori, the space collapses while the images are still loading. Once the images load the entire page reflows and the rest of the content jumps around to make space for them. It would be much better if the space for the images was reserved from the start and, as a bonus, if some lower resolution version of the images displayed, while the images load. Here is how I do it.

Collapse & reflow

You can see the problem in the screenshot from one of my photo posts:

fluid images collapse while loading
Fluid images collapse while loading

There are actually four images in that post but they haven’t loaded yet. The width of the images is set in the layout styles, but the heights depend on the individual images. So we need to find the aspect ratio and generate some low resolution replacement for each image.

To do this, I added some backend image processing to determine the correct height for the images and to generate a low resolution placeholders. You can see the final effect for the same post with the placeholders here.

Base markup & styles

Let’s start with some base HTML for our image.

<div class="img-container">
  <picture class="img-placeholder">
    <img src="image.jpg"/>
  </picture>
</div>

The img-container element will act as a container for the image. The actual image will always be as wide as img-container but the size of the img-container on the page can be changed fluidly according to the layout design. The img-placeholder acts as the placeholder, which enforces the correct height so that the aspect ratio of the image is maintained even when the width changes fluidly. It will also act as a low resolution version while the actual image is loading. Note, that I have used the <picture> element for the placeholder so that I can easily upgrade the markup to use responsive images when needed — you can use a simple <div> too.

Now some CSS to set things up:

.img-container{
  width: 40%;
  /* use a width that fits your layout */
}

.img-placeholder{
  display: block;
  position: relative;
  height: 0;

  background: #aaa;
  /* add your favourite colour as the default background */
  background-size: cover;
}

.img-placeholder img{
  position: absolute;
  width: 100%;
}

I have set the img-placeholder to have height: 0; which will be fixed later with the appropriate padding to get a box with an aspect ratio matching the image.1 The position properties on the img-placeholder and the img inside it makes the actual image fit correctly inside the final padded box. Finally, the background-size: cover; will stretch the low resolution placeholder image to fit the entire box.

Now we need to figure out the correct height for img-placeholder from the aspect ratio of the image and create a low resolution version to act as a placeholder while it is loading.

Image processing in Python

To get the correct height from the image I am using Pillow, an image processing library for Python.2

I’ll assume that the image is being read from a local file called image.jpg and load it into a Python variable called image. Now we can get the height of the image as a percentage relative to its width using the code:3

from __future__ import division
from PIL import Image

## read the image as you want
image = Image.open("image.jpg")

width, height = image.size
height_as_percent = (height/width)*100

The first line forces division in Python 2 to return decimal values instead of integers — if you are using Python 3 you don’t need this.

To generate the low resolution placeholder background, I am simply resizing the original image to a 8×8 image using:

thumb_size = 8, 8
small_image = image.copy()
small_image.thumbnail(thumb_size)

Note that Pillow’s thumbnail function directly changes the original image; so I have made a copy in the variable small_image to keep the original image intact. Also, the thumbnail function automatically preserves the aspect ratio.

At this point we could save the small_image and use it as a background image for the img-placeholder element. But then the browser would have to make an additional request and download this placeholder image. To avoid this I generate a base64 encoded string of the smaller image which is used as a background directly in the HTML. This will increase the size of the HTML but we won’t have to wait for the additional image request.

To generate the encoded string I use:

import base64
import io

buff = io.BytesIO()
small_image.save(buff, format="JPEG")
buff.seek(0)
small_base64 = base64.b64encode(buff.read())
buff.close()

I have assumed that the small_image is a JPEG, but you can change this as needed.

Now I save height_as_percent and small_base64 into a file on disk and, use them when I generate the markup for the image in my templates.

Final markup & result

Now, we use the stored height_as_percent and small_base64 values while generating our HTML. I have used Django-style template variables in the code below; you should use whatever templating system is best for your site.

<div class="img-container">
  <picture class="img-placeholder" style="padding-bottom: {{ height_as_percent }}%; background-image: url(data:image/jpeg;base64,{{ small_base64 }});">
    <img src="image.jpg" />
  </picture>
</div>

The img-placeholder gets a padding-bottom with the value of the computed height_as_percent. This makes a box of the correct aspect ratio for the actual image to sit inside after it loads. I also use the small_base64 string as a background-image which gives a low resolution placeholder until the actual image finishes loading.

Here is a screenshot showing the final effect from the same photo post (compare with the earlier one without placeholders).

Images with low resolution placeholders and once they are fully loaded

As you can see, they aren’t the best looking placeholders since I squeezed the original images into a 8×8 size.

You can play around with the size for small_image to your liking; or adjust the quality settings in the JPEG compression (Pillow defaults to 75) by using:

small_image.save(buff, format="JPEG", quality=50)

For me these are good enough to be just placeholders.

Notes

  1. Read more on the padding trick at Aspect Ratio Boxes by Chris Coyier and Aspect Ratios in CSS are a Hack by Bramus Van Damme .
  2. I’ll be using only some of the more basic functionality of Pillow; for more advanced use Pillow’s documentation is your friend.
  3. I use a similar code to get the aspect ratio of the image as a fraction for the technique I used in Equal Height Images with Flexbox.
Send me (Learn more)

Replies

Eddie Hinkle
replied on eddiehinkle.com with
This is a great read! I’ve been wanting to do placeholder images like this on my website but haven’t had the time to investigate. I’m happy to be able to reference this article now when I get a chance to invest in putting this feature into my website.
Anon
replied on unrelenting.technology with

Nice. I do that as well, but with WebP only (WebP’s header is smaller than JPEG, I remember that Facebook article about their app reconstructing the JPEG header…).

So WebP-supporting browsers get the tiny base64 preview and the full image loading over it, and JPEG-only browsers get a progressive JPEG (mozjpeg-optimized), with the image’s dominant color as the background before the JPEG even starts loading.