API Reference
Imperative control via FluidHandle.
Most of the time, you control <Fluid> with props. When application logic needs
to inject splats, pause the simulation, or read engine state, use the FluidHandle interface.
Getting the Handle
Use Svelte's bind:this to capture a reference to the component instance. The handle property exposes the methods and state below.
<script lang="ts">
import { Fluid } from 'svelte-fluid';
import type { FluidHandle } from 'svelte-fluid';
let fluidRef: { handle: FluidHandle };
</script>
<Fluid bind:this={fluidRef} />
<button onclick={() => fluidRef.handle.randomSplats(5)}>
Add splats
</button> The same pattern works with presets — every preset component exposes the same handle property:
<script lang="ts">
import { LavaLamp } from 'svelte-fluid';
import type { FluidHandle } from 'svelte-fluid';
let lampRef: { handle: FluidHandle };
</script>
<LavaLamp bind:this={lampRef} /> FluidHandle Methods
splat(x, y, dx, dy, color)
Inject a single splat at the given position with velocity and color.
| Parameter | Type | Description |
|---|---|---|
x | number | X position in normalized coords (0 = left, 1 = right) |
y | number | Y position in normalized coords (0 = bottom, 1 = top) |
dx | number | X velocity (raw value, not scaled by splatForce) |
dy | number | Y velocity (raw value, not scaled by splatForce) |
color | RGB | Color in 0-1 linear range. Values above 1 are valid HDR and appear as bloom highlights. |
// Inject a bright red splat at the center moving rightward
fluidRef.handle.splat(0.5, 0.5, 500, 0, { r: 1.5, g: 0.1, b: 0.1 }); Non-finite arguments (NaN, ±Infinity) in position, force, or color are
ignored, with a one-time console warning.
Velocity values are in pixels per frame and written directly to the splat shader uniform.
Unlike pointer-driven splats, these are not multiplied by splatForce.
Pick the absolute magnitude you want. Typical values range from 100 (gentle) to 1000 (forceful).
randomSplats(count)
Push N additional random splats onto the splat queue. Colors are generated by the engine's internal generateColor function.
The queued backlog is capped at 64 and the engine consumes at most 16 per frame, so repeated calls saturate instead of growing without bound. Non-finite or negative counts are ignored.
| Parameter | Type | Description |
|---|---|---|
count | number | Number of random splats to inject |
// Add 10 random splats
fluidRef.handle.randomSplats(10); pause()
Stop the animation loop. The WebGL context stays alive but no frames are requested. Call resume() to restart. Idempotent — calling pause() on an
already-paused engine is a no-op.
fluidRef.handle.pause(); resume()
Restart the animation loop after a pause(). Idempotent — calling resume() on an already-running engine is a no-op.
fluidRef.handle.resume(); isPaused
Readonly boolean property indicating whether the engine's animation loop is currently paused.
if (fluidRef.handle.isPaused) {
fluidRef.handle.resume();
} getPerformanceState()
Return the current autoPerformance governor state. This is pull-based: no events are emitted,
so call it when rendering diagnostics or controls.
const perf = fluidRef.handle.getPerformanceState();
if (perf.enabled && perf.tier !== 'none') {
console.info(`Fluid quality shed: ${perf.tier}`);
} Types
RGB
A simple color triple with r, g, b number fields. The unit convention depends on context:
| Context | Range | Notes |
|---|---|---|
backColor prop | 0-255 | CSS-style. Normalized internally before reaching the shader. |
FluidHandle.splat() color | 0-1 | Linear. Values above 1 are valid HDR and create bloom highlights. |
PresetSplat.color | 0-1 | Linear. Same convention as splat(). |
revealCoverColor, revealAccentColor, revealFringeColor | 0-1 | Linear. |
interface RGB {
r: number;
g: number;
b: number;
} PerformanceState
Snapshot returned by FluidHandle.getPerformanceState().
type PerformanceTier = 'none' | 'pressure' | 'substeps';
type PerformanceAction = 'none' | 'shed-pressure' | 'shed-substeps';
interface PerformanceState {
readonly enabled: boolean;
readonly tier: PerformanceTier;
readonly emaMs: number;
readonly msSinceLastChange: number;
readonly targetFrameMs: number;
readonly pressureIterations: number;
readonly substeps: number;
readonly minPressureIterations: number;
readonly minSubsteps: number;
readonly lastAction: PerformanceAction;
} PresetSplat
Initial splat object consumed once when the engine is created. Preset wrappers use these to paint a repeatable opening scene.
| Field | Type | Description |
|---|---|---|
x | number | X position, 0-1 (left to right) |
y | number | Y position, 0-1 (bottom to top) |
dx | number | X velocity (raw, not scaled by splatForce) |
dy | number | Y velocity (raw, not scaled by splatForce) |
color | RGB | Color in 0-1 linear range (HDR values above 1 are valid) |
interface PresetSplat {
x: number;
y: number;
dx: number;
dy: number;
color: RGB;
} Obstruction
An interior obstacle the fluid flows around, passed via the obstructions prop.
Reuses the same path/text descriptor as the svgPath container variant — the filled
region marks where fluid is blocked. All obstructions union into one combined mask;
the allowed region is container × (1 − obstruction). Orthogonal to containerShape. See Container Shapes and ADR-0034.
| Field | Type | Description |
|---|---|---|
d | string? | SVG path data (path mode). Takes precedence over text. |
text | string? | Text to rasterize (text mode). Centered in the mask. |
font | string? | CSS font for text mode. Default 'bold 72px sans-serif'. |
viewBox | [number, number, number, number]? | viewBox for path mode. Default [0, 0, 100, 100]. |
fillRule | 'nonzero' | 'evenodd'? | Fill rule for path mode. Default 'nonzero'. |
offset | { x: number; y: number }? | 0-1 canvas-space translation after the fit transform. Default { x: 0, y: 0 }. |
scale | number? | Multiplier on the fit scale. Default 1. |
fit | 'contain' | 'fill'? | viewBox→canvas mapping. 'contain' (default) uniform-fits + centers (letterboxes); 'fill' stretches to fill at any aspect (for maze/nozzle channels). Path mode only. |
interface Obstruction {
d?: string;
text?: string;
font?: string;
viewBox?: [number, number, number, number];
fillRule?: 'nonzero' | 'evenodd';
offset?: { x: number; y: number };
scale?: number;
fit?: 'contain' | 'fill';
} FlowConfig
Higher-level scene description for believable presets. It is additive: omitting flow keeps legacy splat behavior unchanged. FluidReveal and FluidDistortion ignore flow visualization because their display shaders are
authoritative. When set, flow.boundary overrides individual canvas edges;
legacy openBoundary is only the fallback for unspecified edges.
interface FlowConfig {
mode?: 'live' | 'prescribed' | 'hybrid';
boundary?: { left?: 'wall'|'open'; right?: 'wall'|'open'; top?: 'wall'|'open'; bottom?: 'wall'|'open' };
sources?: FlowSource[];
outlets?: FlowOutlet[];
scalarFields?: FlowScalarField[];
forces?: FlowForce[];
prescribed?: PrescribedFlowField;
visualization?: FlowVisualization;
} Use FlowSource for persistent point/line/rect emitters, FlowOutlet for edge sponge/drain zones, FlowScalarField for temperature or ink-like
quantities, FlowForce for gravity, pressure-gradient body force, or buoyancy, and PrescribedFlowField for custom uploaded flow grids. In mode: 'prescribed', source velocity and
forces are ignored so the velocity remains exactly prescribed; use 'hybrid' when you want to add live velocity before projection.
Outlets drain fields near an edge; use flow.boundary to decide which
edges are actual pressure-open boundaries.
interface FlowScalarField {
name: string;
dissipation?: number;
advection?: 'standard' | 'low-dissipation';
color?: RGB;
range?: [number, number];
}
type Vec2 = { x: number; y: number };
type FlowForce =
| { kind: 'gravity'; vector: Vec2 }
| { kind: 'pressureGradient'; vector: Vec2 }
| { kind: 'buoyancy'; scalar: string; direction?: Vec2; strength: number; ambient?: number };
interface FlowVisualization {
colorBy?: 'dye' | 'speed' | 'pressure' | 'temperature' | 'scalar';
scalar?: string;
glowBy?: 'speed' | 'scalar' | 'none';
transfer?: 'fire' | 'water' | 'ink' | 'viridis' | 'cfd';
range?: [number, number];
scale?: number;
}
interface FlowGridField {
width: number;
height: number;
data: ArrayLike<number>;
scale?: number;
version?: number | string;
} low-dissipation reduces explicit scalar fading; it is not a MacCormack/BFECC
advection correction. For uploaded grids, replace immutable data objects or bump version when values change so WebGL textures are re-uploaded.
For velocity and pressure plots, FlowVisualization.range maps raw field
values onto the selected transfer function; transfer: 'cfd' provides a
blue→cyan→green→yellow→red engineering-style magnitude ramp.
GL-type-neutral API
As of 1.0 no root-exported declaration mentions a WebGL* DOM type. The internal
types FBO, DoubleFBO, ExtInfo and ResolvedConfig are no longer exported (nothing in the public API returned them). isWebGLAvailable() accepts { failIfMajorPerformanceCaveat?: boolean }.
FluidEngine (Advanced)
For advanced users who need framework-agnostic fluid simulation, the FluidEngine class is exported from the package root. It is the WebGL engine that powers the <Fluid> component, but it has no Svelte dependency.
import { FluidEngine } from 'svelte-fluid'; The engine is constructed with a canvas element and a config object
matching the FluidConfig interface. It exposes the same splat(), randomSplats(), pause(), resume(), isPaused, and getPerformanceState() API as FluidHandle, plus additional methods:
setConfig(config)— update configuration at runtime (respects the 4-bucket system)dispose()— free all GPU resources (textures, framebuffers, programs). The WebGL context is left intact for potential reuse.
Experimental entry
svelte-fluid/experimental exposes engine surfaces built for the Verdigris puzzle
game. No semver promise: any minor release may change or remove them.
LiquidEngine— a free-surface PIC/FLIP liquid on its own WebGL2 canvas, with terrain, three pigment species, exact sensor counts and drains (needsEXT_color_buffer_floatandEXT_float_blend). Its scene, tick and sensor types are exported alongside.createSteppedFluidEngine(options)— aFluidEnginedriven by your clock:advance(steps, dt),renderOnce(), in-placeresize(w, h), and nonblocking air velocity readback withqueueVelocitySnapshot(tick, w, h)/pollVelocitySnapshot(). Pass a snapshot toLiquidEngine.step({ wind })so air deflects falling liquid.
import { LiquidEngine, createSteppedFluidEngine } from 'svelte-fluid/experimental'; Architecture invariant: the engine never imports Svelte. It is fully framework-agnostic and could be used with React, vanilla JS, or any other framework. The Svelte component is a thin wrapper that handles lifecycle, sizing, and prop reactivity.