Pointer · effect

PointerTrail

Marks pressed into the paper behind the cursor, spreading as they soak in.

move the cursor across it

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

Options

paper #fbfaf4

css colour

The sheet the marks are pressed into.

ink #8d8577

css colour

Ink that has soaked in. Most of the trail is this.

accent #c44400

css colour

Ink that has only just landed, at the head of the trail.

marks 22

2 to 28

How many marks the trail holds. The shader has room for 28; asking for more silently gives you 28, because the array is a fixed size and growing it means editing the shader as well.

size 0.032

0.005 to 0.2

Radius of a fresh mark, as a share of the short side, so it keeps its proportion when the element changes shape.

spread 1.6

0 to 4

How much wider a mark gets by the end of its life. Paper draws ink sideways along its fibres, so an old mark is bigger than a new one. This is the opposite of a particle trail and it is the main thing that makes it read as ink.

fade 1.8

0.2 to 6

How quickly a mark gives up. Higher is a shorter trail with a harder end; lower leaves a long tail that never quite goes.

edge 0.85

0 to 1

How much of a mark is its soft shoulder rather than its body. Ink on a fibrous surface has no edge to speak of, so this wants to be high; at 0 you get discs.

spacing 0.035

0.005 to 0.2

Seconds between one mark and the next when the trail is replayed from a scripted path. It has no effect on a live pointer, where one mark is laid per frame.

grain 0.4

0 to 1

Paper tooth under the marks.

What it needs

Dependencies
None at all
Browser
WEBGL2
Frame budget
~1.2 ms · 1 context
Pinned to
v0.11.0
  • An older mark is wider and paler than a new one, because paper draws ink sideways along its fibres while it sinks in. That is the opposite of a particle trail, where older means smaller
  • The marks multiply rather than compositing, so two that overlap are darker than either
  • A trail needs history, which is not a function of t. When pointerPath is set the whole track is known in advance and the trail is read backwards off it instead of accumulated, so a recorded take is identical however the frames are asked for. A live pointer falls back to a ring buffer
  • One WebGL2 context, one full-screen triangle, no buffers and no attributes
  • One distance and one smoothstep per mark per pixel, and the loop stops at the number of marks actually held
  • The mark positions are a single vec2 array uniform, uploaded once per frame
  • Cost scales with the mark count rather than with how far the cursor has moved