Skip to content

Customization recipes

Mix fields, chain transforms, constrain viewports and replace rendering without forking the engine.

Updated View as Markdown

Hyper Flow options are designed to compose. Start with a field topology, choose how each point is painted, then add interaction and frame behavior.

Fine orbital shell

Use the orb field with line particles for a dense optical surface similar to the landing page instrument.

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

const orb = createParticleFlow("#orb", {
  mode: "orb",
  density: 2.2,
  speed: 0.68,
  size: 0.78,
  radius: 0.39,
  depth: 1.16,
  shape: "line",
  lineLength: 5.2,
  lineWidth: 0.62,
  jitter: 0.008,
  viewport: { padding: 0.1, clip: true },
  colors: ["#101c31", "#2454d4", "#0c3699"],
  interaction: { mode: "rotate", strength: 0.42, smoothing: 0.07 },
});

Morph between two fields

mixParticleFields() accepts built-in names or custom field functions. The mix amount can be a number or a function evaluated for every point and frame.

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

const morph = mixParticleFields(
  "torus",
  "rose",
  ({ time, index, count }) => {
    const globalMix = (Math.sin(time * 0.4) + 1) / 2;
    return Math.min(1, globalMix + index / count * 0.12);
  },
);

createParticleFlow("#morph", {
  mode: morph,
  shape: "ring",
  viewport: { padding: 0.12, clip: true },
});

Build a transform pipeline

Pass an array directly or create a reusable transform. Each module receives the output of the previous one. Return false to remove a point.

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

const sculpture = composeParticleTransforms(
  particleTransforms.scale(1.15, 0.72, 1),
  particleTransforms.twist(0.48, 0.06),
  particleTransforms.ripple(0.04, 8),
  particleTransforms.limit(0.94),
);

createParticleFlow("#sculpture", {
  mode: "swarm",
  transform: sculpture,
});

Keep particles inside a component

viewport.padding shrinks the projection area. viewport.clip confines particles, custom renderers and frame hooks to the same inset rectangle.

createParticleFlow("#card-field", {
  mode: "burst",
  radius: 0.46,
  viewport: { padding: 0.14, clip: true },
  particles: { base: 180, min: 80, max: 640 },
});

Use an exact numeric particles value for deterministic captures. Use "auto" to scale density with the canvas.

Ribbon with a fading trace

createParticleFlow("#ribbon", {
  mode: "ribbon",
  density: 1.7,
  speed: 0.78,
  shape: "line",
  lineLength: 7,
  lineWidth: 0.5,
  trail: 0.82,
  blendMode: "lighter",
  interaction: { mode: "repel", strength: 0.24, radius: 1.2 },
});

Trail values are clamped between 0 and 0.98. Disable the trail before exporting a deterministic still frame.

Connected custom topology

Connections are not restricted to the constellation and lattice modes.

createParticleFlow("#network", {
  mode: "stream",
  density: 1.4,
  connections: {
    distance: 0.12,
    opacity: 0.14,
    width: 0.5,
    color: "#2454d4",
    maxPoints: 280,
  },
});

The engine connects depth-sorted neighbors within the configured screen-space distance. Lower maxPoints first when optimizing a large canvas.

Attract points into a tunnel

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

createParticleFlow("#portal", {
  ...particlePresets.portal,
  origin: { x: 0.62, y: 0.5 },
  scale: { x: 1.1, y: 0.82, z: 1.25 },
  interaction: {
    mode: "attract",
    strength: 0.3,
    radius: 1.1,
    smoothing: 0.06,
  },
});

Paint a custom mark

The custom renderer receives projected coordinates. The engine has already applied rotation, perspective, depth sorting and opacity.

createParticleFlow("#marks", {
  mode: "wave",
  renderer(context, point, frame) {
    const length = point.color === 2 ? point.renderSize * 5 : point.renderSize * 2;
    const angle = frame.index * 2.399 + frame.time * 0.2;

    context.strokeStyle = point.colorValue;
    context.lineWidth = point.color === 2 ? 1.2 : 0.55;
    context.beginPath();
    context.moveTo(
      point.screenX - Math.cos(angle) * length,
      point.screenY - Math.sin(angle) * length,
    );
    context.lineTo(
      point.screenX + Math.cos(angle) * length,
      point.screenY + Math.sin(angle) * length,
    );
    context.stroke();
  },
});

Add frame-level drawing

Frame hooks are useful for masks, guides and overlays that should share the engine lifecycle.

createParticleFlow("#instrument", {
  mode: "grid-wave",
  beforeRender({ context, width, height }) {
    context.strokeStyle = "rgb(36 84 212 / 0.12)";
    context.strokeRect(0.5, 0.5, width - 1, height - 1);
  },
  afterRender({ context, width, height }) {
    context.fillStyle = "rgb(16 28 49 / 0.72)";
    context.fillRect(width - 3, height - 3, 2, 2);
  },
});

Switch a scene at runtime

update() accepts every particle option. It keeps the same canvas, observers and controller.

orb.update({
  mode: "infinity",
  shape: "star",
  shapeOptions: { fill: false, strokeWidth: 0.7 },
  parameters: { frequency: 1.25, amplitude: 0.8, twist: -0.4 },
  viewport: { padding: 0.12, clip: true },
  trail: 0.7,
  interaction: { mode: "attract", strength: 0.28 },
});

Call destroy() when the owning component unmounts.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close