MynduiMyndui
0

Anatomy of Warp Starfield

How perspective projection (focal / z), optional warp streaks, and a 0.06 parallax lerp fly a canvas starfield that pauses when you look away.

01/04Perspective: focal / z

Stars surge out from the center as z shrinks — then recycle at the far plane. Count clamps to area / 2200.

Project
k = focal / z
Recycle
z ≤ 0.02 → spawn(maxZ)
Budget
max(80, min(count, area/2200))
tsx
focal = Math.max(w, h) * 0.6;const k = focal / s.z;const sx = cx + s.x * k;const sy = cy + s.y * k;const t = 1 - s.z / maxZ;const size = Math.max(0.4, t * 2.2);const alpha = Math.min(1, t * 1.2);
01

Perspective: focal / z

WarpStarfield keeps a cloud of stars in normalized 3D (x, y ∈ [-1, 1], z ∈ (0, depth]). Each frame it projects them onto the canvas, optionally strokes a hyperspace trail, and eases the projection center toward the pointer. No textures — points (or lines) and a requestAnimationFrame loop.

Stars closer to the camera (small z) project farther from center and draw larger / more opaque. The focal length is derived from the surface:

Every frame s.z decreases by dz. When it dips to ≤ 0.02, the star respawns at the far plane (spawn(maxZ)). Count is clamped with max(80, min(starCount, area / 2200)) so phone-sized heroes don't spawn hundreds of dots.

02/04Cruise vs warp
cruise · fill
warp · stroke

dz = 0.004 × speed × (warp ? 2 : 1)

Cruise
arc + fill · warp=false
Hyperspace
stroke previous → current projection
tsx
const dz = 0.004 * speed * (warp ? 2 : 1);if (warp) {const k2 = focal / pz; // z before this frame's stepctx.moveTo(cx + s.x * k2, cy + s.y * k2);ctx.lineTo(sx, sy);ctx.stroke();} else {ctx.arc(sx, sy, size, 0, Math.PI * 2);ctx.fill();}
02

Cruise vs warp

Default cruise fills a disc. Flip warp and the brush strokes from the previous projected point to the current one — a streak — while dz doubles:

03/04Parallax is a lazy center

The ring is the pointer target; the field lags behind at × 0.06 per frame — never a hard snap. Leave and targets ease back to center.

Lerp
pointer.x += (tx − x) × 0.06
Center
cx = w/2 + x × parallax
tsx
pointer.x += (pointer.tx - pointer.x) * 0.06;pointer.y += (pointer.ty - pointer.y) * 0.06;const cx = w / 2 + pointer.x * parallax; // default parallax = 30const cy = h / 2 + pointer.y * parallax;
03

Parallax is a lazy center

Pointer position is normalized to [-0.5, 0.5] on the container. The draw loop never snaps — it lerps:

Leave the surface and targets ease back to 0. Same offscreen / hidden-tab / reduced-motion gates as Flow Field: stop the rAF when nothing can see it; under reduced motion, paint one still frame.

Star color tracks --foreground (or a color prop) via a theme MutationObserver, so the field stays high-contrast in light and dark.

04/04Result
04

The result

Move across the stage — the vanishing point drifts. The live demo stays in cruise (warp={false}); set warp when you want streaks.

Project, recycle, optionally streak, ease the center. A starfield that costs nothing when it is off screen.

Motion Score

Warp StarfieldCCPaint-triggering
Ccanvas paint2D canvas starfield each frame
Each property is graded by how the browser runs it, from S (composited off the main thread) down to F (layout thrashing); the component takes the worst. MotionScore methodology →