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 |
---|---|
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:
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
.
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
- There’s a ton more that
gatsby-image
does to optimize your images, and you can read more about that in Gatsby’s docs, or see a really cool demo of all the ways you can configure it. - Gatsby starter’s image example
- Medium’s image data inlining technique
- html
picture
element - blur-up technique on css-tricks.com