_______               __                   _______
       |   |   |.---.-..----.|  |--..-----..----. |    |  |.-----..--.--.--..-----.
       |       ||  _  ||  __||    < |  -__||   _| |       ||  -__||  |  |  ||__ --|
       |___|___||___._||____||__|__||_____||__|   |__|____||_____||________||_____|
                                                             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