WBL Code LibraryFlow · 1.1 KB

Blur-up image loading

A tiny blurred version of each photo (a 20 px JPEG, ~0.7 KB inlined) sits in a box of the right shape until the real photo has decoded, then the photo fades in. No grey boxes, no pop, no layout shift.

(re-requests the photos with a 1.5 s delay so you can watch the blur clear)

Copper hair combed through
Red bob
Colour on foils
Nude manicure
The studio floor
Maria at her chair

Each placeholder is a 20 px JPEG inlined as a data URI (~0.7 KB). The box keeps the photo's ratio, so nothing moves when it lands.

How to use it

Size
1.1 KB gzipped own code (JS 0.7 KB + CSS 0.4 KB; 2.1 KB raw)
Dependency
none (vanilla + native APIs)
Tag
Flow
Best for
Every photo below the hero: galleries, work photos, team, review photos

What it does

The figure carries the aspect ratio and the inline LQIP as its background (blurred by a pseudo-element, so the blur never touches the photo). The img starts at opacity 0 only when JS is on; JS waits for load + img.decode() before fading it in, so the fade never shows a half-painted image. Cached photos show instantly; a failed photo shows its alt text instead of a blur forever.

When to use

Don't

Reduced motion, no JS, accessibility

Notes

Copy-ready snippet

<!-- Blur-up image loading (WBL Code Library) -->
<!-- in <head>: -->
<script>document.documentElement.classList.add('wbl-js')</script>
<link rel="stylesheet" href="/wbl/blur-up/module.css">
<script src="/wbl/blur-up/module.js" defer></script>

<!-- markup: -->
<figure class="wbl-bu" style="aspect-ratio:3/2;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDABQODxIPDRQSEBIXFRQYHjIhHhwcHj0sLiQySUBMS0dARkVQWnNiUFVtVkVGZIhlbXd7gYKBTmCNl4x9lnN+gXz/2wBDARUXFx4aHjshITt8U0ZTfHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHz/wAARCAANABQDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwCvbTvHeBAXZGGCuc4NS3WoCCFnXa5BwFzyaPESCyv4JbYBCWDEDoTmql6ojmLxjarHIX0rKCktGa1OV6oYbq4lJZkYegHHFFOU5GTRW6MD/9k=)">
  <img src="../_demo/img/comb-red.jpg" srcset="../_demo/img/comb-red-640.jpg 640w, ../_demo/img/comb-red.jpg 1400w" sizes="(min-width: 900px) 50vw, 100vw"
       width="1400" height="933" alt="Comb through freshly coloured copper hair" loading="lazy" decoding="async">
</figure>

README.md · module.js · module.css · All modules