Show something while loading an image over the network.
Use as little overhead (HTML/JS/bytes) as possible.
Don't block the UI thread.
Progressively enhance the experience.
More thoughts
BlurHash gives us incredible tiny strings, better than other inline base64 encoded options.
A BlurHash includes the average color for an image as well! Easy to extract server-side and inline as a background-color; in my opinion this should't be done in JS client-side.
Paint Worklets work in a background thread (if I understand correctly).
This can all be applied without too much JS overhead, and in a progressive enhanced way. No support? Just ignore (or fall back to the average background color).
Some room for improvement, experimentation and more random thoughts
Become better in basic maths myself (unrelated :)
<link rel="preload" as="paintworklet" href="..."> doesn't work anywhere at the moment, but it should!
Use a Uint8ClampedArray to feed ImageData to putImageData (which currently arent't available in paint worklets), not some shitty fillStyle/fillRect loop (this is my first time using canvas and paint worklets, I'm probably doing stupid stuff; see my first point :)
Previous point is probably why nobody already did this (it's horribly slow).
Still, why does this feel like it's blocking the UI thread? Why is dragging the slider to the left (blowing up the fillRect instructions) killing the UI? [...] Damn you, CPU!
Why is constructor code running twice? [...] Has something to do with the visibility: hidden for this demo. Needs more investigation.
Why does this feel so brittle? Am I holding it wrong?
Chrome doesn't do a background paint when it has the image in its cache (probably an optimization), but for heavy images (like on this page), that still means you could be staring at a white placeholder while the image is being decoded (using decoding="async"). Noticeable while reloading this page with and without a clean cache. Fixable by adding some padding, always triggering a paint.
Will OffscreenCanvas combined with a simple Web Worker make this easier/better? Nah, this should be in CSS as much as possible.
Do some testing on low-end devices. As if performance on a high-end Mac is any good already. Although adding a filter: blur() could make this a viable option. [...] Nope, don't want to have extra elements in the DOM for this, and blurring the resulting image isn't an option. [...] Added a variation that uses a wrapper element and its ::before, which makes this pretty usable, even with quite pixelated versions (=low amount of fillRect calls).
It's possible to create linear gradients out of BlurHashes. Pretty cool, but doesn't really scale. What if we play with createLinearGradient inside the paint worklet. [...] Nope, you still need to blur these pixels somehow.
Play around with paint arguments; custom properties feel like a useless step. style="background-image:paint(blurhash,'LaKnl3-:xuIU~qRPogIU%MD%M{t7');" perhaps makes more sense in this case.
Why don't browsers natively implement a blurhash: 'LaKnl3-:xuIU~qRPogIU%MD%M{t7'; CSS thingy?! Or even better, a blurhash="…" attribute on the img, which paints a background-color and blurry background-image automatically? Sign me up!
How to add the CSS.paintWorklet.addModule line in the most non-render-blocking way? Deferring the JS makes it too late for CSS. Putting it in the head blocks the parser. Hm. Preload? Shouldn't this be added via CSS anyway? Smart people have probably figured this out already, although everything I've read about the CSS Paint API so far has all been related to demos. Who's using this in production already?
[...] But, base64 encoding a blurry image as a WebP background results in ~200 character background images. Not sure why I went with BlurHashes in the first place :P
I should have a blog. Nah, this doesn't count as blogging. Or does it? Or is this a blog already? It's a static page. Oh, that's all hip and trendy again. Jamstack! Anyway, hit me up on Twitter.
View source to see my messy implementation. You can probably do better :)