_______ __ _______
| | |.---.-..----.| |--..-----..----. | | |.-----..--.--.--..-----.
| || _ || __|| < | -__|| _| | || -__|| | | ||__ --|
|___|___||___._||____||__|__||_____||__| |__|____||_____||________||_____|
on Gopher (inofficial)
URI Visit Hacker News on the Web
COMMENT PAGE FOR:
URI Minimal CSS-only blurry image placeholders
turnsout wrote 23 hours 54 min ago:
Man, it's wild how much you can do with CSS calculations. How long
before someone makes a CSS-only Game Boy emulator?
WorldMaker wrote 15 hours 10 min ago:
I did find this Idle Game entirely in CSS amusing:
URI [1]: https://lyra.horse/css-clicker/
Cieric wrote 23 hours 54 min ago:
Just in case anyone also misses it like I did, dark reader (at least on
firefox) appears to apply itself to the final colors causing them to
look quite bad and not match the input image at all. I would have
discounted this entirely if it wasn't for all the praise I was seeing
in the comments here.
bmandale wrote 1 day ago:
My attempt at the four color approach:
URI [1]: https://0x0.st/820Q.html
thwarted wrote 1 day ago:
No one remembers the lowsrc img attribute.
bufferoverflow wrote 1 day ago:
We need to embrace WebP v2 for this kind of stuff. I took one of their
images, resized it to 24x16px, and compressed it with Squoosh at 65%
quality. It compresses to just 144 bytes. And it looks way way way
better than these CSS gradients.
URI [1]: https://squoosh.app/
Lord_Zero wrote 1 day ago:
Cool app but no maintained library to use it in our own apps and
scripts.
bufferoverflow wrote 1 day ago:
URI [1]: https://github.com/GoogleChromeLabs/squoosh
molszanski wrote 1 day ago:
Amazing work! Thanks you for sharing!
layer8 wrote 1 day ago:
I read the title as âMinimal CSS â only blurry image
placeholdersâ first by mistake. ;)
ssttoo wrote 1 day ago:
Another simple css-only solution as the article mentions is gradients.
Like
background: linear-gradient(
to right,
#51463e 0%,
#28241f 100%
);
Tool:
URI [1]: https://tools.w3clubs.com/gip/
duffyjp wrote 1 day ago:
Years ago before you could do anything this fancy with CSS I
experimented with generating 3x2 pixel images server side and then
presenting them as base64 encoded pngs in a "scoped" block of CSS to
ensure they loaded before the src images. Coincidentally this was the
same 3x2 layout as OP did here with CSS. I abandoned it because a 3x2
image scaled up looked terrible, and went with average color instead.
This solution looks a lot better visually.
I still do the average color thing today since it's easy to calculate
and store server side (I resize the uploaded image to 1x1 px and just
record the result as a hex code in the DB).
WorldMaker wrote 1 day ago:
It's obviously mostly an aesthetic nitpick for this blog post
specifically and not the project itself, because few people are going
to be exploring the encoded space outside of the blog post, but the
sliders letting you explore the LQIP space would "flash" a lot less if
the base color was encoded in the high bits instead of the low bits.
jbverschoor wrote 1 day ago:
That's sexy!
mattdesl wrote 1 day ago:
Really like this, nice work!
Something to note is that Color Theif (Quantize) is using median cut on
RGB, it would be interesting to try and extract dominant color in OKLab
instead.
I also love the idea of a genetic algorithm to find an ideal match for
a given image; it should be possible to simulate radial gradients
server & client side with webgpu, but probably overkill for such a
simple task.
EDIT: Although it works for me in Chrome, it doesn't seem to work in
Safari v16.1.
rckt wrote 1 day ago:
Nice, but... it's not actually minimal. But nice.
Also a bit of nitpicking. While it provides a visual placeholder for an
image that's being fetched, it does not reflect its content. So, when
it's loaded we can see a completely different color palette and shapes.
tempoponet wrote 1 day ago:
I see two issues, let's say "opportunities":
First is the limitation to one hue value. Something like the
Sunflower (blue + yellow) is just yellow. Maybe there's a tradeoff
that could pack more hue but with less luminescence.
The second is how the primary color is selected. Several images
(plant on grey background, street food vendor) appear to be averaging
across the image and getting a grey value. By selecting better for
the predominant color and its placement, the greys would appear on
their own.
mary-ext wrote 1 day ago:
That's pretty much an issue with every LQIP solutions though,
including BlurHash and ThumbHash. The only thing that matters is that
it's close enough to the real thing, since they're meant to serve as
placeholders.
pavlov wrote 1 day ago:
What do you mean? In my opinion this library does a very good job of
representing the imageâs color palette considering itâs encoded
into a single integer. (Even smaller than usual because of CSS
limitations, only 20 bits!)
You donât even need JavaScript to decode that integer into the
image. The underlying CSS may be complex, but for the user of the
library it definitely feels minimal in a good way.
rckt wrote 1 day ago:
In the gallery [1] there's a good example of what I mean - the
bottom right image or 4th from the end. A completely different
image in comparison to the gradient.
As for the minimalism, I understand what you mean. But I understood
the "minimal" part in regard to implementation, not usage. If we
only mean usage, we can say the same about a lot of libs, that they
are minimal. Yeah, it's minimal for the end user, but under the
hood it is not as minimal. It's not anything bad, it's just how I
interpreted the title.
URI [1]: https://leanrada.com/notes/css-only-lqip/gallery/
chmod775 wrote 1 day ago:
Cool hack, but performance is terrible. That page makes scrolling on my
phone laggy.
emsixteen wrote 1 day ago:
Forgive my ignorance, feel like it's embarrassing to ask here to be
honest, but can someone explain how this helps/works? I've never
actually used these placeholders, but I always imagined that they work
by processing the image beforehand on the server and using something
like a super low quality image or gradient or such as the placeholder.
If this is done in pure CSS, does the browser not need to download the
image first to figure out what's in it, before then doing the
placeholder effect? Perhaps it doesn't help that I've not had my
morning coffee yet, but I don't understand.
simonw wrote 1 day ago:
Here's the server-side (Node.js) build script that calculates the
integer placeholder image values and adds them to the document:
URI [1]: https://github.com/Kalabasa/leanrada.com/blob/7b6739c7c30c66...
diiiimaaaa wrote 1 day ago:
These placeholders are generated by processing the image on a server
beforehand. Generally they create some html, css or svg markup that
is served inline. Having to do a separate request for such
placeholder is very bad idea.
It's not clear if these placeholders do actually help, especially
placeholders with very low quality. In my opinion, they only add
visual noise.
I'd focus more on avoiding layout shifts when images load, and
serving images in a good format (avif, webp) and size (use `srcset`
or ``).
WorldMaker wrote 15 hours 16 min ago:
There are certainly legitimate cases where the placeholders come
from a separate request. Most
CRDTs and similar sync engines you donât want to (and/or are not
allowed to) store binary images directly inside them, so you need
to store references to some other blob storage. But Blurhash is a
simple short string (and LQIP here is a simple integer) and those
store well in CRDTs and other sync engines, so you can pair that
with your reference pointer (which might not even be a URL
depending on your blob storage engine and its sync mechanics and
authorization schemes and whatever else) and whatever other
metadata you want/need to include like width/height or aspect ratio
and alt/title/caption.
When the CRDT or document sync engine inevitably sync much faster
than your blobs you have something to show in that placeholder. If
the blob sync fails for some reason, you still have something to
show more interesting than your browserâs old broken image logo
under your âSync is slow or brokenâ warning.
I think placeholders help a bunch in situations like that where
your image fetch is a lot more complicated than a URL that you can
add in a `src` attribute. Itâs also really easy to get into
situations where such blob fetching is complex: In cases where you
have to respect user and/or tenant privacy and need complex OAuth
flows. In cases where you need end-to-end photo encryption. In
cases where you need peer-to-peer sync and only P2P sync because
youâve been mandated to reduce touch points and likelihood of
accidentally storing photos at rest in middle layers. Situations
like images of HIPAA data, PII, PIFI, etc.
On a static site with public (or cookie sessioned) images direct
linked by URL, yeah the placeholders donât do much other than
check certain design boxes. Thereâs lots of other places images
(and their metadata) come from, and placeholders are a useful
fallback in the worst cases.
biker142541 wrote 1 day ago:
> It's not clear if these placeholders do actually help
Well, it depends what you mean by help. Itâs very dependent on
use case and desired UX. Obviously you can prevent layout shifts
without them, you can provide feedback on loading status in other
ways, and ensure images donât slow load time without colored
placeholders. But they can provide a pleasant UX for some use
cases, when done right. They can be annoying when not done well.
JimDabell wrote 1 day ago:
Itâs still computed at build time or dynamically, by a programming
language. The âpure CSSâ part of it means that the hash is
decoded into something visual by CSS without any JavaScript required.
dmitrygr wrote 1 day ago:
cool, but the fact that you can now do this with CSS is part of the
reason that a new browser engine is so unlikely - one of 100000 things
that css can do now and need to be supported :(
Maybe we should have kept CSS simple and JS optional. Maybe we took a
few wrong turns...
cjpearson wrote 1 day ago:
It's all additive so each new feature does indeed add complexity, but
my impression is that it's often the older features and all their
quirks which are the most difficult to implement. Adding a few math
functions is much easier than ensuring compatibility with CSS2
floats.
naveed125 wrote 1 day ago:
This is pretty neat
matthberg wrote 1 day ago:
Since there're independent Lightness values set for each section (I'd
say quadrant but there are 6 of them), I wonder if two bits can be
shaved from the `L` value from the base color. It'd take some
reshuffling and might not play well with color customization in mainly
flat images, but I think it could work.
I'm also curious to see that they're doing solely grayscale radial
gradients over the base color instead of tweaking the base color's `L`
value and using that as the radial gradient's center, I'd imagine you'd
be doing more math that way in the OKLab colorspace which might give
prettier results(?).
Tempted to play around with this myself, it's a really creative idea
with a lot of potential. Maybe even try moving the centers (picking
from a list of pre-defined options with the two bits stolen from the
base color's L channel), to account for varying patterns (person
portraits, quadrant-based compositions, etc).
biker142541 wrote 1 day ago:
This works significantly better than I would have expected. I was just
exploring extremely simple png strings as an alternative to the hash
libraries requiring decoding. I had also explored two color css
gradient, based on pregenerated major/minor colors, but too course to
be useful (for a fast scrolling gallery). Iâll give this a test
drive!
cynicalsecurity wrote 1 day ago:
Why is the page so sluggish on mobile?
simonw wrote 1 day ago:
Probably because of all of the wildly complex CSS calculations it's
running, as described by the article.
Kalabasa wrote 1 day ago:
Yep, there are a lot of layers and compositing operations (maybe
more than necessary?). I suppose it could be simplified further.
benfortuna wrote 1 day ago:
..or use tailwind -
URI [1]: https://tailwindcss.com/docs/filter-blur
jsheard wrote 1 day ago:
That's not at all equivalent to what the OP is doing. The point isn't
just to blur an image, which is what those Tailwind classes do, the
point is to render a very compact blurry version of an image which
hasn't loaded yet.
Zensynthium wrote 1 day ago:
Love the website and article! Looks like even with CSS there's always
new things to learn and do, good stuff.
esprehn wrote 1 day ago:
This is really cool, I love seeing folks use CSS in clever ways. :)
My one feedback would be to avoid using attr selectors on the style
attribute like [style*="--lqip:"]. Browsers normally lazy compute that
string version of the style attribute [1], but if you use a selector
like this then on every style recalc it'll force all new inline styles
(ex. element.style.foo = bar) to compute the string version.
Instead if you use a separate boolean attribute (or even faster a
class) it'll avoid that perf foot gun. So write and match on that.
URI [1]: https://source.chromium.org/chromium/chromium/src/+/main:third...
cAtte_ wrote 1 day ago:
see also the author's last note on the upcoming parsing feature of
`attr()`, which would solve both problems (performance and verbosity)
at once:
cendyne wrote 16 hours 9 min ago:
I can't wait for the day when attr has this implemented. It would
eliminate the need for so many inline styles
mike2323 wrote 1 day ago:
broken on iOS (iPad)
thangngoc89 wrote 1 day ago:
Also broken for me:
Safari 18.0 (20619.1.26.31.6), macOS Sequoia 15.0
alwillis wrote 23 hours 35 min ago:
Works fine on macOS Sequoia 15.4 with Safari 18.4.
tlb wrote 1 day ago:
Me too. Thumbnails just appear black.
Safari 17.6 (19618.3.11.11.5), MacOS Sonoma 14.7.3 (23H417)
It works on Chrome on the same machine.
simonw wrote 1 day ago:
Worked for me in Mobile Safari in iOS on my iPhone.
wruza wrote 1 day ago:
Same setup, didn't work. (Empty space where blur supposed to be.)
VladVladikoff wrote 1 day ago:
Maybe itâs iOS version dependant. Iâm a bit out of date (on
purpose for jailbreak) and the demo is broken for me.
whstl wrote 1 day ago:
I'm up to date and it's broken for me :/
davidmurdoch wrote 1 day ago:
This is brilliant!
superkuh wrote 1 day ago:
I suppose the existence of bad uses does not invalidate the good but it
feels like 99% of blurry image placeholder behavior is actually just
preventing people from seeing anything unless they also run the ad and
spying javascript that monetizes the site.
So a CSS-only way is neat and indisputably better but I think it's
missing the point? The point of blurry placeholders isn't to make
things easier or display better. The point is to make things worse.
This write up is definitely making things better.
nirava wrote 1 day ago:
It is really simple to make sure your blurred css placeholder is
cosmetic, and a progressive enhancement. I would know, I wrote one a
month ago for my personal site.
My goal was to have something that'd transmit all the essential bits
of the site in the first 14kB, and worked basically on everything. It
wasn't hard, honestly.
It wasn't a particularly complex site but i guess what I'm saying is
any well done (and well intentioned) implementation of blurred image
placeholders will works with or without JS. That is just sound
engineering...
recursive wrote 1 day ago:
The placeholder is inline in the markup. It can be displayed before
the image loads. Which is not inline. I have no idea what 99% thing
you're talking about.
superkuh wrote 1 day ago:
Look at literally any "newspaper" website. From the smallest local
paper to the NYT.
recursive wrote 1 day ago:
That's a whole separate thing with a different reason for
existing.
simonw wrote 1 day ago:
The point of blurry placeholders is to support loading a page with
potentially hundreds of images (maybe with additional lazy loading,
which doesn't need JavaScript these days) without blocking display of
the page on loading those full images.
I'm not sure why you think it has anything to do with forcing people
to execute JavaScript?
jasonkester wrote 1 day ago:
Indeed. So the visitor need only wait for the 20mb javascript
bundle, but not the 600kb of images, before he can see the 1kb of
text that he visited the site to read.
simonw wrote 1 day ago:
Sounds like you're in favor of a version of blurry placeholders
that's implemented in less than 1KB of CSS.
wruza wrote 1 day ago:
Can't speak for everyone ofc, but not sure if I ever wanted blurry
placeholders when images load fast enough, or found them anything
but annoying when not. I think these bells and whistles only serve
as designer's self-affirmation.
gblargg wrote 1 day ago:
Agreed, they just create needless visual activity. How about a
page specify where the images appear, and leave it up to the
browser to decide how to show them and load them? Is that too
simple and workable?
simonw wrote 1 day ago:
I've definitely wanted them on photo galleries with large numbers
of thumbnails, and I appreciate them when they are implemented
well, especially if I'm on a slow connection.
throwaway2016a wrote 1 day ago:
Very nice solution!
Definitely very low resolution, but compared to sites that use a solid
color this seems much better. And only requiring one variable is really
nice.
The article seems very well thought through. Though for both the
algorithm and the benchmark algorithm the half blue / half green image
with the lake shows the limitations of this technique. Still pretty
good considering how light weight it is.
8n4vidtmkvmk wrote 1 day ago:
The half blue / half green image still looks better with LQIP than
BlurHash. I was getting ready to use BlurHash in my app, might try
this instead!
In fact, LQIP looks better than most of the BlurHash examples in the
gallery ( [1] ); not sure if these were cherry picked or what.
URI [1]: https://leanrada.com/notes/css-only-lqip/gallery/
Kalabasa wrote 1 day ago:
Author here: Definitely cherry picked ;)
I did deliberately pick some "bad" examples like the blue+green
image, and other multicolor images.
I wanted to add an upload function so people could test any image,
then i realised I'd have to implement the compression/hashing in
the client. Maybe i should!
throwaway2016a wrote 1 day ago:
I could tell and I really appreciate it. It's really helpful to
see both the good and the bad.
Great work!
simonw wrote 1 day ago:
I tried getting that working earlier using Claude to convert your
script - you can see the result here: [1] Here's the transcript
and code:
URI [1]: https://claude.site/artifacts/b747d94a-2923-4904-8ed1-73...
URI [2]: https://claude.ai/share/4a562082-b681-4f0c-909c-3c32c34f...
seejayseesjays wrote 1 day ago:
this is super neat! love your site
mubou wrote 1 day ago:
Was expecting the common "background-image: data url + filter: blur"
that a lot of static site generators produce, not a binary encoding
algorithm implemented in CSS! Very impressive.
I wonder what other things could be encoded this way. Those generic
profile pictures, perhaps? (The ones where your email or account id is
hashed to produce some unique geometric pattern.)
Reubend wrote 1 day ago:
It's a cool solution, and I like that it's CSS only. But the generated
placeholders are way too blurry/lossy for my personal preferences.
ipunchghosts wrote 1 day ago:
I know very little of css and to me it seems like a configuration file
for rendering text, similar to changing default fonts ornsizes for
matplotlib plots using plt.rcParams. How does this do inage blurring
then?
maxbond wrote 1 day ago:
> [It] seems like a configuration file for rendering text...
A more accurate mental model might be, "a declarative language for
styling HTML elements," where "styling" is very broad. You can make
user interfaces that show and hide elements, have animations, etc.
triggered by clicking buttons without a single line of JavaScript.
It's a lot more powerful than the configuration parameters to
plotting functions, in my book it's a programming language rather
than a configuration language.
teraflop wrote 1 day ago:
If you read the article, it explains exactly how the technique works.
One of the many ways CSS allows you to customize formatting is to
change the background style of elements. In addition to just using a
solid color or image, you can specify a procedural gradient. And by
superimposing several such gradients, you can make a very blurry
approximation of an image.
CSS also includes a basic expression language which allows evaluating
simple arithmetic expressions. So you can encode all the blurred
image's parameters as a packed integer in a single compact CSS
property per image, and use rules to define the gradients in terms of
that integer.
Note that CSS is not used to compute the blurred image representation
itself -- you have to do that separately. (Even if you could do it in
pure CSS, the whole point is to show a blurred preview image before
the image itself is downloaded to the browser, so doing it in CSS
would defeat the purpose.)
DIR <- back to front page