HalftoneReveal

A picture arriving dot by dot, the way a halftone comes up on press.

press replay to run it again

Paste into Claude Code, Cursor or any agent. Pinned to a tag, so it will not change under you.

Options

paper #fbfaf4

css colour

Shown wherever a dot has not grown yet, which at the start is the whole frame. Match it to the page behind or the reveal begins as a visible panel. `auto` takes it from the page, so the effect adopts whatever it is dropped onto.

screen 14

4 to 48

Cell size in CSS pixels. Bigger cells are a coarser screen and a more obviously printed arrival. Laid out in pixels rather than in UV so the dots stay round and keep their size when the element changes shape.

angle 45

0 to 90

Screen angle in degrees. 45 is the one a printer reaches for, because a screen on the square reads as a grid and fights whatever is underneath it.

sweep 24

0 to 360

Direction the reveal travels, in degrees. 0 runs left to right. Only visible when scatter is below 1.

scatter 0.55

0 to 1

0 is a clean directional sweep, 1 is a random dissolve with no direction at all. Between the two the sweep keeps its direction but its leading edge is ragged, which is the part worth having.

order sweep

sweep · centre · edges · shadows · highlights

The queue the cells arrive in. sweep runs across the frame along `sweep`; centre works outward from the middle and edges closes inward from the border; shadows brings the dark areas up first, which is the order a press lays ink down in, and highlights does the reverse, which reads as a picture emerging out of the paper rather than being printed onto it.

shape round

round · square · diamond

The shape of the dot. All three are screens a press has actually used: round is the default everywhere, square holds its shape into the shadows instead of merging, which is why newspapers used it, and diamond breaks up the jump at fifty percent where round dots all meet their neighbours at once and the midtone goes abruptly dark.

feather 0.55

0.05 to 1

How much of the reveal has cells part way through at any one moment. Low is a hard edge travelling across; high has the whole frame coming up together.

grain 0.4

0 to 1

Paper tooth over the whole thing.

duration 1800

200 to 8000

Milliseconds from blank paper to the finished picture.

What it needs

Category
Reveals, an effect
Dependencies
None at all
Browser
WEBGL2
Frame budget
~0.6 ms · 1 context
Pinned to
v0.13.0
  • The picture is the host element's own <img> child. It is hidden from sight and left in the document, so the alt text is whatever you wrote and a page with no JavaScript still shows it
  • The two tonal orders read the tone at the pixel rather than at each cell centre, which would be nine more texture samples. The difference is sub-cell on a photograph and the ordering is a soft field, so it costs nothing visible
  • renderAtTime is pure in t, so replaying is a matter of resetting the clock rather than restarting anything
  • One WebGL2 context, one full-screen triangle, no buffers and no attributes
  • One texture read and one hash per pixel, and a union over the nine cells around it. The neighbours are what stop a dot past half a cell being clipped square
  • The image uploads once, on load. Nothing is uploaded per frame
  • The screen is laid out in CSS pixels, so the cost is per pixel and does not change with the cell size