Progressive Image Loading - Blurry Placeholder Image like Unsplash
Unsplash is my favorite image site, not only for it’s huge amount of free images but also for it’s great user-experience. It has already been quite a while since Unsplash started using BlurHash to delivery progressive Image Loading experience.
The Concept
Progressive Image Loading
Progressive Image Loading makes better user experience. But what is Progressive Image Loading?
Instead of waiting for the final image to be rendered, use a low-quality image as placeholder (LQIP) first, swith to the original one when it’s loaded.
What is going on?
- Render an element (
div
for example) with specificaspect-ratio
, usually the same as the original image - Render a low-quality image first
- When this element is in viewport, triggers loading the original image
- After the original image is loaded, renders it upon the low-quality image and then hide/remove the low-quality image on the background
Placeholder vs. the Original
⬅️ This 8 × 6 px image can be scaled up many times (865 × 649 px on my screen for example) without increasing noticable noises.
What does Unsplash do?
- Fetch list data using XHR
- Decode BlurHash from
blur_hash
field of a list item - Generate a 8 x 8 px
image/bmp
image from canvas drew with decoded BlurHash data, rendered as image placeholder - Render the original image when it’s loaded
Why BMP?
See Size Comparison.
Also, render a small-sized image comsumes less CPU and Memory usage than draw a canvas do.
How to Generate Blurry Image?
Photoshop is an great tool to handle filter effects (such as Gaussian Blur) but that would be too complicated for most users.
So I created a web app (Image Blurrer) to generate blurry placeholder image. 4 types of output image supported:
Comparison
Image Quality
With my Image Blurrer, blur-radius can be customized when generating blurry image using StackBlur. In my personal usage and test, StackBlur is my favorite, due to it’s flexible blur-radius.
Cavas Width | Blur Radius |
---|---|
32 px | 2 px |
BlurHash in most cases is a fine option (can be proved from the experience of Unsplash), however, in some cases it renders the dark area of the original image just too dark.
Example:
Original | BlurHash |
---|---|
https://r2-assets.thelynan.com/uPic/P1010249.jpg | UcF~mn?b00D$~q-:IUM{?c%3M{RjWBWBt7xu |
Image Size
Test Image: https://r2-assets.thelynan.com/uPic/progress-image-loading-test-img.jpg
I’ve hilighted the smallest size in my tet result. Over all in my test, image/bmp
is the smallest when canvas width is below 10px, and then between 12px - 22px, image/png
takes over, after that image/jpeg
is the smallest.
width(px) | jpeg | png | bmp |
---|---|---|---|
4 | 816 B | 132 B | 90 B |
6 | 828 B | 168 B | 135 B |
8 | 825 B | 222 B | 198 B |
10 | 846 B | 279 B | 279 B |
12 | 867 B | 366 B | 378 B |
14 | 885 B | 432 B | 495 B |
16 | 882 B | 546 B | 630 B |
18 | 936 B | 618 B | 783 B |
20 | 954 B | 768 B | 954 B |
22 | 942 B | 855 B | 1.1 KB |
24 | 1.0 KB | 1.0 KB | 1.3 KB |
26 | 1.0 KB | 1.1 KB | 1.5 KB |
28 | 1.1 KB | 1.3 KB | 1.8 KB |
30 | 1.1 KB | 1.4 KB | 2.0 KB |
32 | 1.1 KB | 1.6 KB | 2.3 KB |
Actually, most browsers do not support generating image/bmp
directly by canvas.toDataURL, image/png
format will be used if the specified type is not supported.
Solutions:
- canvs-to-bmp
- jimp My choice, for I have already use it to generate CSS gradient, also in my practice, jimp generate smaller-sized bmp image file.
1 | const img = await Jimp.read("./path/to/image.jpg"); |
Thanks to
Progressive Image Loading - Blurry Placeholder Image like Unsplash