Flow Field

Hundreds of particles ride a seeded, slowly changing vector field and leave fine fading trails, as a seamless loop

Installation
$ pnpm dlx shadcn@latest add @remocn/flow-field

Usage

FlowField draws particles that follow a vector field across the frame. The field angle at each point is a sum of four seeded sine waves, and it shifts slowly over the loop. Each particle is born at a seeded position, sometimes just outside the frame, and steps along the field for its life. Its head moves first and the trail grows behind it; then the head stops and the tail catches up, so trails grow in and drain out instead of popping. Births are spread over the life, so the frame always holds a steady population.

Each trail is split into six age segments that get fainter and thinner toward the tail. A seeded share of particles, accentRatio, is drawn in accent.

import { FlowField } from "@/components/remocn/flow-field";
 
export const Texture = () => <FlowField />;

The component fills its parent and paints its own background. Particle speed and stroke width scale with the short side of the composition, and strokeWidth is in px at 720p. Positions come from Remotion's seeded random(), so a seed always produces the same field and every frame renders the same way.

Timing

The field and every particle's life cycle repeat every loopFrames / speed frames, 300 frames (10 seconds) at the defaults. The last frame of the loop flows into the first without a seam, so a Sequence of any multiple of the period, or a Remotion <Loop> of that length, plays as a continuous background.

A particle lives loopFrames / round(loopFrames / lifeFrames) frames, 150 at the defaults, so a whole number of lives fits in one loop. The trail is capped at 60% of the life: the head travels for life − trail frames, then holds while the tail drains.

There is no build-up: frame 0 already shows a full population.

Performance

Each frame recomputes every visible trail from the particle's birth, so the cost grows with particles × lifeFrames. The defaults render quickly; 800 particles with 300-frame lives is about five times heavier. Lower particles before lowering trailLength if renders get slow.

Variations

A high trailLength with a low particles count gives long, sparse strands. accentRatio={0} keeps the field monochrome, and accentRatio={1} draws everything in the accent. Change seed for a different field layout.

<FlowField
  particles={160}
  trailLength={110}
  strokeWidth={2}
  accentRatio={0.25}
  background="#0b1020"
  color="#9fb4ff"
  accent="#ffffff"
  seed={5}
/>
Props
PropTypeDefaultDescription
particles
number300Particles in flight
trailLength
number72Trail length in frames of travel, capped at 60% of the particle life
loopFrames
number300Frames for one seamless loop of the field and the particle life cycles
lifeFrames
number150Particle lifetime in frames, adjusted so a whole number of lives fits in loopFrames
accentRatio
number0.1Share of particles drawn in the accent color, 0 to 1
strokeWidth
number1.4Trail width in px at 720p, at the head; older segments thin to half
seed
number1Seeds the field, the spawn points and which particles take the accent
background
string"#111111"Background fill
color
string"#FFFFFF"Trail color
accent
string"#E5322D"Color of the accent share of trails
speed
number1Playback multiplier; the loop period is loopFrames / speed