Stoyan (@stoyanstefanov) is a Facebook engineer, former Yahoo!, writer ("JavaScript Patterns", "React: Up and Running"), speaker (JSConf, Velocity, Fronteers), toolmaker (Smush.it, YSlow 2.0) and a guitar hero wannabe.
Wouldn’t it be nice to show a preview of an image while it’s still being loaded? And without any additional HTTP requests and with just a tiny amount of CSS? Enter Gradient Image Placeholders. Or GIP for short. Pronounced jipp as not to be confused with the image format GIF which (there are no two opinions about it!) is pronounced “gif” as in gift.
gradient placeholder | actual image (credit) |
How would an image tag look like in this case?
<img src="https://example.org/image.jpg" style="background:linear-gradient(135deg, #cbc6c2 0%, #5d5347 100%)" width="300" height="450" />
Is this a good idea?
Progressive rendering === good. At least this is a common assumption, although we’re light on actual studies. In fact, we have one study that shows progressive rendering may increase the cognitive load on the user, and is, therefore, bad. Though the validity of the study is under question. Review of the sources here. So we’re back to square one.
If you agree that progressive rendering helps with the perceived performance (what?, how?) then read on!
Welcome to the family
The GIP technique is in the same family of efforts as Low Quality Image Placeholders (LQIP) and SQIP (SVG version of LQIP, see here too). Low quality placeholders have been around for a long time. I personally noticed Picassa was doing it way back in 2009, for who knows how long.
Both SQIP and LQIP can produce images that are as small as 400 bytes after compression. And the benefit of SQIP is that SVG images can be inlined with no extra HTTP requests or extra base64 overhead.
GIP also requires no HTTP requests and the added bytes are about 60 before compression, chances are the payload is even smaller given the repetition of the strings background
and linear-gradient
and to bottom
, etc.
The drawback is that the GIP placeholders are not as detailed as SQIP ones, but does that really matter? I don’t really know.
Tools?
I was mulling over the GIP technique a while ago and came up with a clumsy tool which I posted on Twitter. And people pointed me to something that already existed – grade.js (code). Grade.js takes the two most prominent colors of an image and draws a diagonal gradient between them. OK, good start, but could be improved quite a bit.
Also in the last calendar edition I saw Tobias’ article that had a suspiciously familiar screenshot, much closer to what I had in mind. He pointed me to a tool called gradifycss.com that is now domain-dead, but the code is still around and, in fact, the demo is still usable thanks to archive.org (and hooray for client-side programming!).
I thought it’s time to dust off my old demo and “productize” it. The results are very similar to what gradify does. I sometimes like mine better, sometimes not. There’s still room for experimentation.
How is the gradient figured out?
First, to figure out prominent colors in an image, quantization is important, because if you blindly count the exact occurrences of each pixel, you often end up with black or white. Just because a photo of an ocean, for example, doesn’t have the exact same blue all that often. The colors shades flow much nicer and blend between each other. So often just a handful of pure black pixels overwhelm and “win” over hundreds of various shades of blue.
Now, to figure out how to do the gradient I split the image into 4 equal parts: top left, top right, bottom right, bottom left. Then quantize each of the pieces and figure out the top 4 colors in it. Then take the top one color. This way I end up with 4 colors, one for each “corner”.
(Wait, why bother finding the top colors and then pick the first one only? Well, when you quantize, the number of colors in the palette you specify does affect the results. Through experimentation I found that palette size of 4 seems to yield the most satisfying results. Though there’s a PALETTESIZE
constant in the code that you can fiddle with and let me know.)
Then I find the two colors that are furthest apart and draw a gradient between them. So the CSS gradient can be in one of the 4 directions:
to bottom
(top to bottom),to right
(left to right),135deg
(top left to bottom right diagonal),45deg
(bottom left to top right diagonal).
(Wait, why top-to-bottom but not bottom-to-top, for example? Well, since the two colors are the same, the direction is irrelevant.)
Tools!
Node module and CLI
To integrate the GIP technique into your build tools, you can use the spanking new NPM module called cssgip. The code is on github.
Install…
npm i -g cssgip
…and use the CLI:
gip test.png
This spits out something like:
background: #3f8cc4; background: linear-gradient(to bottom, #0f7ad2 0%, #b0adbe 100%)
As you can see the tool also gives you the overall background color, in case you:
- worry about browsers that don’t understand gradients
- rather use a single color than a gradient
This module can be used without the CLI, of course:
const gip = require('cssgip'); const result = await gip('./image.jpg');
The result is an object with three properties like:
css: "background: #ab9f92; background: linear-gradient(135deg, #cbc6c2 0%, #5d5347 100%)" background: "#ab9f92" gradient: "linear-gradient(135deg, #cbc6c2 0%, #5d5347 100%)"
Browser version
You can use GIP in the browser too. Say, an article author uploads an image into your CMS and you figure out the colors right then and there.
The browser version is available as an NPM module too and has 0 dependencies, technically, because the quantization dependencies are inlined and stripped of the unnecessary bits. The code is on github too. The build, minified and gzipped is under 4K.
Usage
Old school…
<script src="gip.js"></script> <script> const result = gip(img); </script>
… or new school import
:
import gip from 'cssgip-browser'; const result = gip(img);
The img
can be an object created with new Image()
or document.createElement('image')
or found in the DOM like document.images[0]
(talking of old school!) or document.getElementsByTagName('image')[0]
or document.getElementById('the-logo')
and so on.
The object returned by gip(img)
has three properties, like so:
css: "background: #ab9f92; background: linear-gradient(135deg, #cbc6c2 0%, #5d5347 100%)" background: "#ab9f92" gradient: "linear-gradient(135deg, #cbc6c2 0%, #5d5347 100%)"
Up to you what to do with these results.
Demo
Finally, there’s a demo site where you can try your own images. The demo is using the browser version of GIP (client-side only) so your images are not uploaded to The Cloud (hooray for privacy!).
After you pick an image you can click to toggle between the image and the gradient:
The demo also spits out the palettes of the 4 “corners” and the overall image palette for debugging purposes, in case you want to try things out and make the tool better.
Your turn
Feel free to try and then adopt the GIP technique in your build tool and CMS. Webpack, WordPress plugins… up to you!
Also, there’s more to explore and potentially improve the tools. Maybe split up the image further (top-middle-bottom and left-center-right) and use other CSS properties for even more detailed preview. Radial gradients, maybe? CSS is so powerful these days that there may very well be even more convincing ways to do placeholders.
The tools once more:
And thank you for reading!