GitHub

Geometric image placeholders for Svelte 5

No blur. The placeholder is the image, approximated by geometric shapes fitted at build time — and because they're stored in fit order, replaying them makes the picture visibly sharpen until the real photo takes over, cut into the same shapes.

In production on szkolyjogi.pl — 700+ listing photos resolve from their geometric preview — and kurcz.pl.

A page of photos

throttled on purpose — each photo lands in its own time, and its shapes hold the box until it does

Photos on Unsplash by Marat Gilyadzinov, Alexey Topolyanskiy, Samuel Scrimshaw, veeterzy, Andrew Coelho, André Spieker.

Playground

fitted live in your browser — in production this runs at build time
Geometrized preview
1200×800
drag back to watch it sharpen — takeShapes(placeholder, n) sends only the first n
Inline placeholder
2.6 KB
2.6 raw
Lighter first paint
—
vs. the photo
Shapes
100
in fit order
Fit time
build
prebuilt
Image
Pick any photo — it's geometrized on your device, nothing is uploaded.
Preset
a named look — sets the shape types and opacity below
Shape types
Reveal
how each shape animates in — replays on change
Object fit
cover crops; contain letterboxes — SVG matches the photo
more shapes = more detail, bigger payload
lower = more layered, translucent shapes
optional early-exit when the approximation is “good enough”

Presets

one photo, five looks — ?preset=soft&geometrize
triangles 2.6 KB
low-poly 2.6 KB
soft 2.3 KB
mosaic 1.9 KB
bubbles 1.7 KB

How it works

a real preview in a couple of KB — no blank box, no layout shift, no blur
  1. 01

    Fit at build time

    A Vite plugin fits geometric shapes to each ?geometrize import — cached on disk by content hash, so clean rebuilds skip work you've already done. Or batch CMS photos with npx svelte-geometrize. The expensive part never ships.

  2. 02

    Paint instantly, no layout shift

    The placeholder is inline in the server-rendered HTML, so a real preview fills the box before the photo's first byte arrives — and because its aspect ratio is reserved, nothing below it ever jumps.

  3. 03

    Reveal, then hand off

    revealMs paces the shapes across a fixed budget; pure CSS plays before hydration and respects reduced motion. Then the photo opens through the same shapes, smallest first, and fills the gaps between them. Nothing is ever half see-through.

Use it

register the Vite plugin, then import with ?geometrize
import { GeometrizedImage } from '@nomideusz/svelte-geometrize';
import placeholder from './photo.jpg?geometrize';
import src from './photo.jpg';

<GeometrizedImage {placeholder} {src} alt="Sunset over mountains" revealMs={850} />

// a named look: './photo.jpg?preset=soft&geometrize'

// build / ingest also need: pnpm add -D sharp
// npx svelte-geometrize ./photos --out ./placeholders

Full options and the Node API are in the docs.