Lightning Fast Image Previews with Pure CSS and LQIP
My website is reasonably fast.
I hope that every page load feels snappy, no matter your device or location. That should not come as a surprise. After all, I’m just using plain HTML and CSS. JavaScript is avoided whenever possible.
There was one thing left, which really annoyed me: layout reflow after images got loaded.
The problem is, that the image dimensions are not known when the text is ready to be displayed. As a result, the text will be pushed down on the screen as soon as an image is loaded above.
Also, while an image is loading, there is no preview, just blank space. Here’s what that looks like on a slower connection:
I could fix that, by hardcoding the image width and height, but that would be tedious and error-prone. And there would be no preview. So I was wondering, what others were doing. 🤔
Tiny image thumbnails
I vaguely remembered, that Facebook uses tiny preview thumbnails in their mobile app. They extract the quantization table from the JPEG header to render the preview. This information is stored on the client, so it doesn’t need to be downloaded every time. Unfortunately, this approach requires full control over the image encoder. It works for apps, but hardly for websites.
The search continued.
Until my colleague Tobias Baldauf introduced me to LQIP (Low-Quality Image Placeholders).
Here’s the idea:
- Load the page including inlined, low-quality image thumbnails.
- Once the page is fully loaded (e.g. when the
onload
event is fired), lazy load full quality images.
Unfortunately, this technique requires JavaScript. Nevertheless, I liked the idea, so I started experimenting with different image sizes and formats. My goal was to create the smallest thumbnails using any standard image format.
Benchmark
Here are 15 pixel wide thumbnails encoded in different file formats:
I used different tools to create the thumbnails. For JPEG and PNG encoding, I used svgexport.
For webp, I used cwebp:
The gif was converted using an online tool and optimized using gifsicle:
Comparison
WebP is the smallest, but it’s not supported by all browsers.
Gif was second, but when resizing the image and applying the blur filter, I was not happy with the result.
In the end, I settled for PNG, which provided an excellent tradeoff between size and quality. I optimized the images even further using oxipng, which supports zopfli compression. With that, I end up with thumbnails of around 300-400 bytes in size.
I integrated the thumbnail creation process into my build toolchain for the blog. The actual code to create the images is rather boring. If you really want to have a look, it’s on Github.
Avoiding JavaScript
Here is the skeleton HTML for the image previews:
The trick is to wrap both the full-size image and the preview image into a loader
div, which gets a width: auto
CSS attribute:
}
I wrap the SVG into an object
tag instead of using an img
element. This has the benefit, that I can show a placeholder in case the SVG can’t be loaded. I position the object
at the top left of the loader
div.
}
}
Here’s the placeholder hack including some references:
/* https://stackoverflow.com/a/29111371/270334 */
/* https://stackoverflow.com/a/32928240/270334 */
}
}
The last part is the handling of the thumbnails. Like most other sites, I decided to apply a blur filter. In a way, it looks like the image is frozen, so that’s what I called the CSS selector. I also applied a scaling transformation to achieve sharp borders.
}
{
0% }
100% }
}
I use CSS animations instead of JavaScript.
The duration of the animation is based on the 95% percentile load time of all visitors of the page. Although it’s just an approximation, this should work for most readers.
Result
- No JavaScript needed
- Works on all modern browsers
- Supports a fallback in case the main image can’t be loaded
- Tiny overhead
Resources
- Introducing LQIP – Low Quality Image Placeholders
- How Medium does progressive image loading
- SQIP, a new preview technique using pure SVG
Thanks for reading! I mostly write about Rust and my (open-source) projects. If you would like to receive future posts automatically, you can subscribe via RSS.