CSS Based Low Quality Image Place Holders

I have implemented a CSS based low quality image placeholder (lqip) system. What I implemented comes from this extensive blog post breaking it all down.

You can view a demo of it here:

How it works

The summary of how it works is that images are processed and a lqip style variable number is applied to each image, such as --lqip:-179989. This is a hash value that determines what the background gradient should look like, and it is accurate enough to the elements in the image.

Some fancy CSS decoding of this value is done to generate the background gradient of the image that then becomes the low quality image place holder.

I added it as part of my 11ty-image optimization build step. I made it work with remote images as well by retrieving the buffer created by 11ty fetch and passing that into the lqip generation function

I stripped as much of the code that wasn’t necessary for generating the lqip value. I even tried to just use the sharp dominant color instead of the Colorthief dominant color, but the gradients weren’t accurate so I kept this as well.

Caching

All of this processing, 11ty image + generating the lqip, starts to add up the more images there are on a site.

The first thing I did was implement a dictionary to check if an lqip value had been generated for an image already. This works well the same is used multiple times on a site, but it would still require to generate the lqip value each time.

After reading the 11ty fetch docs I found out that I could manually cache my own things. So I cached the generated lqip value.

With both of these optimizations, image processing is minimal. That being said, it is important to cache the .cache folder and the optimized images output within cloud cannon. You want these folders to persist between builds to reduce build times. My cache settings for my sites look like
node_modules,.cache,dist/assets/images

If you are using netlify, you can use the netlify plugin cache for caching.

Final thoughts

Overall this isn’t something that most people will notice. Internet speeds these days means that the lqip may show up for less than a second. My main reason for implementing it is just because I thought it was cool.

Code

Image optimization + lqip generation:

The lqip generation code:

The css:

5 Likes

That’s really cool, @Gio! It’s one of those subtle touches that might fly under the radar for most users, but for the ones who do notice, I feel like it adds a huge layer of polish and thoughtfulness to the experience. Awesome work!

1 Like

Really nice solution @Gio! I think “because it’s cool” is the best reason tbh. I also scroll too fast sometimes, so appreciate the LQIP rather than image pop-in, even on fast connections.

For anyone using Hugo (v 0.104.0+), there’s a neat method involving $image.Colors to return a slice of hex strings of dominant colors, with which you can apply a similar effect.

A few links if anyone’s interested:

  • See the effect in action at staticbattery.com (you may need to throttle your connection speed)

  • GitHub repo of the gallerydeluxe module to see the setup. Essentially it shows a gradient of the top 2 colors in the image, then a 20x20px version with a CSS blur filter, then the full image.

  • More info on .Colors on the Hugo docs

2 Likes

That’s really cool! I love the image fade 20x20 pixel image variant. I briefly played around with having the image fade in once it was done loading instead of the horizontal loading. I didn’t figure it out after a few hours and decided that, while nice to have, not needed. I was happy enough with the gradient. I may revisit this idea in the future, maybe.

2 Likes