Skip to content

Particle fields

Tune fine point clouds, depth, projection, interaction, custom math and Canvas rendering.

Updated View as Markdown

Hyper Flow pixels are fine points, not large retro blocks. Every built-in field returns normalized 3D coordinates. The renderer transforms, projects and depth-sorts them before painting one of ten Canvas 2D shapes.

Built-in fields

Mode Character
pulse A breathing spherical point cloud
orbit Four independently tilted trajectories
orb Layered orbital shells made for fine line particles
wave A rippling spherical surface
helix Three interlaced strands
vortex A rotating spiral search field
constellation A sparse linked thought map
scatter A cloud that breaks and reforms
lattice A rotating volumetric point grid
ribbon Seven parametric strands moving as one surface
tunnel Rings traveling through perspective depth
stream Parallel animated signal lines
torus A rotating ring volume with layered bands
rose A parametric field of layered petals
swarm An organic coordinated point cloud
grid-wave A spatial grid displaced by travelling waves
burst Radial signals travelling from the center
infinity Several strands woven into a continuous loop
import { createParticleFlow } from "@scorpion7slayer/hyper-flow";

const field = createParticleFlow("#field", {
  mode: "orb",
  density: 1.8,
  speed: 0.65,
  size: 0.8,
  radius: 0.39,
  depth: 1,
  perspective: 3.2,
  shape: "line",
  lineLength: 5.4,
  lineWidth: 0.65,
  viewport: { padding: 0.1, clip: true },
  colors: ["#101c31", "#2454d4", "#0c3699"],
  opacity: [0.2, 0.96],
  rotation: { x: -0.18, y: 0, z: 0 },
  interaction: { mode: "rotate", strength: 0.42 },
});

Choose a particle primitive

The default renderer supports circles, squares, diamonds, triangles, lines, crosses, rings, dashes, stars and configurable polygons. size scales every point without changing field density.

field.update({
  shape: "polygon",
  size: 0.8,
  density: 1.6,
  shapeOptions: {
    sides: 7,
    fill: false,
    strokeWidth: 0.7,
    rotation: Math.PI / 7,
    aspect: 0.72,
  },
});

field.update({
  shape: "line",
  lineLength: 7,
  lineWidth: 0.55,
});

The DPR is capped at 2 by default. Set pixelRatio to a number when a capture or display needs an explicit value.

Constrain the drawing viewport

Use fractional viewport padding to keep perspective projection and custom rendering inside a demo, card or product surface.

field.update({
  viewport: {
    padding: 0.12,
    clip: true,
  },
});

clip: true also confines beforeRender, renderer and afterRender drawing to the padded rectangle.

Add trails and compositing

trail controls how much of the previous frame remains. 0 clears every frame and 0.98 approaches a persistent trace. Combine it with any Canvas 2D blendMode.

field.update({
  mode: "ribbon",
  shape: "line",
  trail: 0.82,
  blendMode: "lighter",
});

Keep trails below 0.9 for interfaces where the field must settle quickly after pointer input.

Configure connections

connections: "auto" preserves the default links for constellation, lattice and grid-wave fields. Set false to remove them, true for standard links on any topology, or pass exact values:

field.update({
  connections: {
    distance: 0.14,
    opacity: 0.16,
    width: 0.55,
    color: "#2454d4",
    maxPoints: 320,
  },
});

Change pointer behavior

The pointer can rotate the whole field, repel nearby points or attract them. smoothing controls how quickly the force catches up with the pointer.

field.update({
  interaction: {
    mode: "repel",
    strength: 0.34,
    radius: 1.15,
    smoothing: 0.08,
  },
});

Pass interaction: false for a purely ambient field.

Compose the field inside the canvas

Use origin to move the projection center, scale to stretch individual axes and jitter to add controlled organic displacement.

field.update({
  origin: { x: 0.7, y: 0.48 },
  scale: { x: 1.25, y: 0.72, z: 1 },
  jitter: 0.018,
});

Use parameters to change time and spatial behavior across built-in and custom fields. The standard keys are frequency, phase, amplitude, spread, and twist. Any additional numeric key is passed to custom field functions unchanged.

field.update({
  speed: -0.7,
  parameters: {
    frequency: 1.4,
    amplitude: 0.72,
    twist: -0.5,
    customNoise: 0.18,
  },
});

Write a field function

mode accepts a function. It runs once per point and per frame:

createParticleFlow("#field", {
  parameters: { curl: 1.4 },
  mode: ({ index, count, time, parameters }) => {
    const progress = index / count;
    const angle = progress * Math.PI * 12 + time * (parameters?.curl ?? 1);
    return {
      x: Math.cos(angle) * progress,
      y: progress * 2 - 1,
      z: Math.sin(angle) * progress,
      color: index % 3,
      size: 0.8 + progress,
      opacity: 1 - progress * 0.35,
    };
  },
});

Transform a built-in field

Use one transform or an array when the built-in topology is useful but its geometry needs local rules. Returning false culls the point for that frame.

import { createParticleFlow, particleTransforms } from "@scorpion7slayer/hyper-flow";

createParticleFlow("#field", {
  mode: "wave",
  transform: [
    particleTransforms.twist(0.4),
    particleTransforms.ripple(0.05, 7),
    ({ opacity = 1 }) => opacity < 0.25 ? false : undefined,
  ],
});

Replace point painting

Use renderer(context, point, frame) to replace the built-in painter. Projected screenX, screenY, renderSize, and resolved colorValue values are included on point. Canvas state such as globalAlpha already reflects depth and point opacity.

Use beforeRender(frame) and afterRender(frame) to paint a background grid, mask or foreground treatment inside the same clipped frame. Pass null through update() to remove a transform, renderer or hook.

See Customization recipes for complete orb, ribbon, connection and renderer examples.

Performance and accessibility

  • Automatic density scales with the shorter canvas side and defaults to a 1,200 point maximum. particles can set an exact count or a custom range up to 4,000.
  • The DPR defaults to the device value, capped at 2.
  • ResizeObserver keeps the backing buffer aligned with CSS size.
  • IntersectionObserver and page visibility pause offscreen work.
  • reducedMotion: "respect" renders a stable frame instead of a continuous loop.
  • destroy() removes observers, pointer listeners and the animation frame.

Set autoPause: false or reducedMotion: "ignore" only when the application has a specific reason.

Stepped DOM motion

The earlier CSS-style pixel timing remains available for DOM targets through flow():

flow(".sprite", {
  from: { x: -31, opacity: 0 },
  to: { x: 0, opacity: 1 },
  duration: 560,
  pixel: { steps: 7, snap: 4 },
});

This DOM option changes easing and transform snapping. It is separate from the Canvas particle engine.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close