Controls
Circles
10
Stroke
6
Focal
1000
Max R
920
Dash
40
Motion
Back to gallery
Experiment2025Interactive

Parallax Circles

Move your pointer. Each ring drifts on its own parallax factor while counter-rotating on its own clock. The same component drives the section backgrounds elsewhere on this site.

Move the cursor across the page to nudge the field.

How it works

A single pair of motion values tracks the pointer. Each ring derives its center from those values multiplied by a per-layer factor — the outer rings move more than the inner ones, which sells the depth without any 3D math.

Rotation is independent: every ring picks a random duration and direction on mount and loops forever. Strokes are dashed and use pathLength="100" so the dash pattern stays consistent regardless of radius.

Perspective model

Sizing isn't hand-tuned per ring — it falls out of a pinhole-camera formula. Each ring sits at a world depth d between focalLength (the nearest) and focalLength + worldDepth (the farthest), and its projected radius is (focalLength · maxRadius) / d.

That single ratio gives the whole composition its "lens":

  • Short focal length — wide-angle. Near rings dominate, far rings shrink hard. Strong depth.
  • Long focal length — telephoto. Near and far compress toward the same size. Flatter, calmer.

Parallax follows the same ratio: each ring's pointer factor is proportional to its projected radius, so near rings drift more than far ones for free.

Motion patterns

The component accepts a motionPattern prop. Three flavors of follow:

  • spring — bouncy, the default. Overshoots slightly and feels alive.
  • ease — exponential lerp every frame. Quieter, no overshoot.
  • direct — no smoothing. Snappy, twitchier, more honest about the input.

Variants

Same component, different lenses. Each tile shares geometry — only focalLength and the motion pattern change. Move across them to feel the depth shift.

Wide-angle · Spring
Standard · Ease
Telephoto · Direct