The Daily TIL

February 26, 2019 by AndyAndyimagesgatsbycsshtmlprogressiveblur-up

Progressive image loading is complicated

I originally discovered progressive image loading while learning the amazing Gatsby site generator. I have some large above-the-fold images on my site that were causing some pretty-distracting content-shifts as they loaded.

My goal was to reduce the UI shifting with a placeholder that would be replaced with the loaded image. Turns out, this is basically the idea behind Progressive Image Loading.

Here’s an example of loading an image normally vs. progressively,

Normal img Progressive
before after

Since I’m building a Gatsby site, I am using the built-in Gatsby-Image component to achieve out-of-the-box progressive loading.

The default placeholder image is generated using a super-low-res or “blur-up” version of the image you want to load. This works pretty well without any configuration, but I wanted to know how it actually worked.

Example

Looking at the HTML generated by gatsby-image is a good place to start here. This is a simplified version of what gatsby-image generates when you use a fluid image, but the important parts for understanding basic progressive loading concepts are here.

<div
  class=" gatsby-image-wrapper"
  style="position: relative; overflow: hidden;">
  <div style="width: 100%; padding-bottom: 56.25%;" />
  <img
    src="data:image/jpeg;base64,/9j/...f/Z"
    style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; opacity: 0; transition: opacity 0.5s ease 0.5s;"
  />
  <picture>
    <img
      sizes="(max-width: 1600px) 100vw, 1600px"
      srcset="/static/.../4c886/fill_murray.jpg  400w,..."
      src="/static/.../b0a71/fill_murray.jpg"
      style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; object-fit: cover; object-position: center center; opacity: 1; transition: opacity 0.5s ease 0s;"
    />
  </picture>
</div>

Even in this simplified version, there’s a lot going on here. Let’s take it piece-by-piece.

The gas-filled bladder

 <div
    class=" gatsby-image-wrapper"
    style="position: relative; overflow: hidden;"
  >
    <div style="width: 100%; padding-bottom: 56.25%;" />

The outer div class="gatsby-image-wrapper" here defines an outer container for all the image components, and the first child of this container is an empty div that serves as the swim bladder of the progressive image. Without it, the whole image would not have anything to define its size and would just kinda collapse. It “inflates” itself by setting a width so that it fills its container, and padding-bottom so it maintains a 16:9 aspect ratio (padding-bottom: calc(9/16*100%) => 56.25%).

All other elements in the container will be absolutely positioned so they can be overlaid on the swim-bladder.

The opening act

<img src="data:image/jpeg;base64,/9j/...f/Z"
     style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; ..."
/>

This is the element that makes the image “progressive”. It’s the blurry placeholder you see when the real image is loading. You can see that it’s a real image that has an inline base64 src. If you opened up this image in a new browser tab, you’d see this:

ScreenCapture at Thu Feb 28 12:53:04 PST 2019

This is called a blur-up placeholder. There are other types of progressive placeholder techniques, but this is probably the most common. Probably because the placeholder itself is so tiny that it barely impacts page load times when inlined as img src.

The clever thing here is that when you take this tiny copy of your real image, and stretch it to 100% of the container size, you get a super-blurry copy of your original image. Here’s that tiny image stretched to width: 300px.

ScreenCapture at Thu Feb 28 12:56:52 PST 2019

Hence, “blur-up”.

The swap

<img ... style="... opacity: 0; transition: opacity 0.5s ease 0.5s;" />

You might also notice the easing transition applied to the placeholder. This is so when the actual image finishes loading, the transition from placeholder to real image is a bit less jarring. It will fade out as the real image fades in.

The band you came to see

<picture>
  <img
    sizes="(max-width: 1600px) 100vw, 1600px"
    srcset="/static/.../4c886/fill_murray.jpg  400w,..."
    src="/static/.../b0a71/fill_murray.jpg"
    style="...transition: opacity 0.5s ease 0s;"  />
</picture>

The real star of the show is obviously the image you want to load. I won’t go into the specific of the <picture> tag here, but you can read more about it at the MDN docs. The one thing to point out about this image, is again, the css transition. This helps with the swap explained above and ensures that the real image eases in as the placeholder eases out.

Once the real image fades in, that’s it!

References