@jayf0x/fluidity-js 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +148 -0
- package/CONTRIBUTING.md +45 -0
- package/README.md +170 -94
- package/dist/globals.d.ts +7 -2
- package/dist/index.js +782 -740
- package/llms.txt +156 -0
- package/package.json +34 -6
package/AGENTS.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# AGENTS.md — fluidity-js
|
|
2
|
+
|
|
3
|
+
> Universal agent instructions for GitHub Copilot, Cursor, GPT-4, Gemini, and all AI coding assistants.
|
|
4
|
+
> Claude Code users: see [CLAUDE.md](./CLAUDE.md) for deeper architecture notes.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## What this repo is
|
|
9
|
+
|
|
10
|
+
`@jayf0x/fluidity-js` — a WebGPU-first fluid simulation library for React. Implements a real-time Navier-Stokes solver (advection, divergence, pressure, vorticity confinement) that renders onto a `<canvas>` element. Falls back automatically to WebGL2 → WebGL1. Runs the heavy simulation loop in a Web Worker via OffscreenCanvas by default.
|
|
11
|
+
|
|
12
|
+
Two components: `<FluidText>` (text as obstacle/background) and `<FluidImage>` (bitmap as obstacle/background).
|
|
13
|
+
|
|
14
|
+
**Stack:** TypeScript · React 17+ · Vite 4 · Vitest · Bun
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Repo layout (critical paths)
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
src/
|
|
22
|
+
index.ts ← public exports
|
|
23
|
+
globals.d.ts ← ambient global types (FluidConfig, FluidHandle…)
|
|
24
|
+
core/
|
|
25
|
+
config.ts ← DEFAULT_CONFIG, presets, mergeConfig
|
|
26
|
+
gl-utils.ts ← WebGL context + FBO helpers
|
|
27
|
+
gpu-utils.ts ← WebGPU helpers
|
|
28
|
+
shaders.ts ← GLSL shader strings
|
|
29
|
+
wgsl-shaders.ts ← WGSL shader strings
|
|
30
|
+
simulation.ts ← FluidSimulation class (dual WebGPU/WebGL)
|
|
31
|
+
textures.ts ← texture creation for text + image modes
|
|
32
|
+
worker/index.ts ← Web Worker message handler
|
|
33
|
+
fluid-controller.ts ← worker vs main-thread abstraction
|
|
34
|
+
react/
|
|
35
|
+
FluidText.tsx ← React component
|
|
36
|
+
FluidImage.tsx ← React component
|
|
37
|
+
useFluid.ts ← core hook: canvas lifecycle + controller
|
|
38
|
+
tests/ ← Vitest + jsdom (83 tests)
|
|
39
|
+
demo/ ← standalone Vite 5 demo site (NOT the library)
|
|
40
|
+
dist/ ← built output (do not edit)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Commands
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Run tests (library root, Node 16 compat)
|
|
49
|
+
bun test:claude # preferred: runs vitest, tails last 8 lines
|
|
50
|
+
|
|
51
|
+
# Build library
|
|
52
|
+
bun build
|
|
53
|
+
|
|
54
|
+
# Demo (requires Node 20)
|
|
55
|
+
cd demo
|
|
56
|
+
PATH=/Users/me/.nvm/versions/node/v20.19.6/bin:$PATH bun dev
|
|
57
|
+
PATH=/Users/me/.nvm/versions/node/v20.19.6/bin:$PATH bun build
|
|
58
|
+
PATH=/Users/me/.nvm/versions/node/v20.19.6/bin:$PATH bun deploy # → gh-pages
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Public API
|
|
64
|
+
|
|
65
|
+
### Components
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
<FluidText text="Hello" fontSize={120} color="#fff" algorithm="glass" preset="neon" />
|
|
69
|
+
<FluidImage src="/hero.jpg" algorithm="aurora" quality={{ dpr: 1, sim: 0.5 }} />
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### FluidHandle ref
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
interface FluidHandle {
|
|
76
|
+
reset(): void;
|
|
77
|
+
move(opts: { x: number; y: number; strength?: number }): void;
|
|
78
|
+
splat(x: number, y: number, vx: number, vy: number, strength?: number): void;
|
|
79
|
+
updateConfig(config: Partial<FluidConfig>): void;
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Algorithms
|
|
84
|
+
`'standard'` · `'glass'` · `'ink'` · `'aurora'` · `'ripple'`
|
|
85
|
+
|
|
86
|
+
### Presets
|
|
87
|
+
`'calm'` · `'sand'` · `'wave'` · `'neon'` · `'smoke'`
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Code style / conventions
|
|
92
|
+
|
|
93
|
+
- **TypeScript strict** — no `any`, no non-null assertions without a comment
|
|
94
|
+
- **No file extensions** on imports within `src/` (e.g. `from './config'`)
|
|
95
|
+
Exception: worker import keeps `.js?worker&inline` for Vite query string
|
|
96
|
+
- **No default exports** from library modules; named exports only
|
|
97
|
+
- **No comments** unless the *why* is non-obvious (hidden constraint, invariant, workaround)
|
|
98
|
+
- **Formatting:** Prettier with `@trivago/prettier-plugin-sort-imports`
|
|
99
|
+
- **Tests:** All 83 must pass before any commit. Add tests for new behaviour.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Architecture invariants (do not violate)
|
|
104
|
+
|
|
105
|
+
| Rule | Reason |
|
|
106
|
+
|------|--------|
|
|
107
|
+
| `transferControlToOffscreen` called at most once per canvas | Irreversible — double-mount in StrictMode would crash |
|
|
108
|
+
| `useFluid` creates a **fresh** `<canvas>` element each mount inside a container `<div>` | StrictMode safety |
|
|
109
|
+
| `createBlit` sets up vertex buffers **once** | Performance — never call `vertexAttribPointer` per draw |
|
|
110
|
+
| Worker `.terminate()` always deferred 50ms after `postMessage({type:'destroy'})` | Lets the worker flush its destroy sequence |
|
|
111
|
+
| Display shader outputs **premultiplied alpha** | Transparent canvas compositing correctness |
|
|
112
|
+
| Coverage texture ref: `text` mode → `coverageTex === obstacleTex`; guard against double-delete | Memory safety |
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## What agents MUST NOT do
|
|
117
|
+
|
|
118
|
+
- **Do not** edit files under `dist/` — these are build artefacts
|
|
119
|
+
- **Do not** add `// eslint-disable` or `@ts-ignore` without explaining why in the PR
|
|
120
|
+
- **Do not** introduce new peer dependencies without updating `peerDependencies` in `package.json`
|
|
121
|
+
- **Do not** call `vertexAttribPointer` inside the render loop
|
|
122
|
+
- **Do not** call `transferControlToOffscreen` more than once per canvas element
|
|
123
|
+
- **Do not** use `window.*` APIs directly inside `worker/index.ts` (worker context)
|
|
124
|
+
- **Do not** commit if `bun test:claude` fails
|
|
125
|
+
- **Do not** change the module format from ESM to CJS — consumers depend on tree-shaking
|
|
126
|
+
- **Do not** modify `CLAUDE.md` unless explicitly asked — it is the authoritative agent guide
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Testing notes
|
|
131
|
+
|
|
132
|
+
- Tests run under jsdom with a WebGL mock (`tests/setup.js`). `navigator.gpu` is absent — all tests exercise the WebGL path.
|
|
133
|
+
- `FluidText`/`FluidImage` are `forwardRef` ExoticComponents — check `$$typeof`, not `typeof`.
|
|
134
|
+
- React component mocks require `.ts` extension: `vi.mock('../../src/fluid-controller.ts', ...)`
|
|
135
|
+
- Worker mock: `vi.mock('../src/worker/index.ts?worker&inline', ...)`
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Where to find more
|
|
140
|
+
|
|
141
|
+
| Resource | Path |
|
|
142
|
+
|----------|------|
|
|
143
|
+
| Deep architecture guide (Claude-specific) | [CLAUDE.md](./CLAUDE.md) |
|
|
144
|
+
| Prioritised bug/feature backlog | [backlog.md](./backlog.md) |
|
|
145
|
+
| Version history | [changelog.md](./changelog.md) |
|
|
146
|
+
| Live demo source | [demo/src/examples/](./demo/src/examples/) |
|
|
147
|
+
| npm package | https://www.npmjs.com/package/@jayf0x/fluidity-js |
|
|
148
|
+
| Live demo | https://jayf0x.github.io/fluidity |
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Contributing to fluidity-js
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
git clone https://github.com/jayf0x/fluidity
|
|
7
|
+
cd fluidity
|
|
8
|
+
bun install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Run tests:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bun test:claude
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Run the demo (requires Node 20):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
cd demo
|
|
21
|
+
PATH=/Users/me/.nvm/versions/node/v20.19.6/bin:$PATH bun install
|
|
22
|
+
PATH=/Users/me/.nvm/versions/node/v20.19.6/bin:$PATH bun dev
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Before opening a PR
|
|
26
|
+
|
|
27
|
+
- All 83 tests pass (`bun test:claude`)
|
|
28
|
+
- No new peer dependencies without prior discussion
|
|
29
|
+
- Code formatted with `bun format`
|
|
30
|
+
- See [AGENTS.md](./AGENTS.md) for code style and architecture constraints
|
|
31
|
+
|
|
32
|
+
## Where to look
|
|
33
|
+
|
|
34
|
+
| Goal | Files |
|
|
35
|
+
|------|-------|
|
|
36
|
+
| Add/change a visual effect | `src/core/shaders.ts`, `src/core/wgsl-shaders.ts`, `src/core/simulation.ts` |
|
|
37
|
+
| Add a new prop | `src/globals.d.ts`, `src/react/FluidText.tsx` or `FluidImage.tsx`, `src/core/config.ts` |
|
|
38
|
+
| Add a preset | `src/core/config.ts` |
|
|
39
|
+
| Fix a simulation bug | `src/core/simulation.ts`, `src/core/gl-utils.ts`, `src/core/gpu-utils.ts` |
|
|
40
|
+
| Fix React lifecycle | `src/react/useFluid.ts`, `src/fluid-controller.ts` |
|
|
41
|
+
| Fix worker communication | `src/worker/index.ts`, `src/fluid-controller.ts` |
|
|
42
|
+
|
|
43
|
+
## Reporting issues
|
|
44
|
+
|
|
45
|
+
Use the GitHub issue templates — include a minimal repro snippet and your browser/renderer info.
|
package/README.md
CHANGED
|
@@ -1,32 +1,51 @@
|
|
|
1
|
-
# fluidity-js
|
|
1
|
+
# fluidity-js — WebGPU Fluid Simulation for React
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@jayf0x/fluidity-js)
|
|
3
|
+
[](https://www.npmjs.com/package/@jayf0x/fluidity-js)
|
|
4
|
+
[](https://www.npmjs.com/package/@jayf0x/fluidity-js)
|
|
5
|
+
[](https://bundlephobia.com/package/@jayf0x/fluidity-js)
|
|
4
6
|
[](./LICENSE)
|
|
5
|
-
[](./tsconfig.json)
|
|
8
|
+
[](https://github.com/jayf0x/fluidity/actions/workflows/ci.yml)
|
|
9
|
+
|
|
10
|
+
**Real-time Navier-Stokes fluid dynamics for React — interactive water, ink, glass, and aurora effects on text and images. WebGPU-first with automatic WebGL2/WebGL1 fallback. Runs in a Web Worker for zero jank.**
|
|
6
11
|
|
|
7
12
|
<a href="https://jayf0x.github.io/fluidity">
|
|
8
13
|
<p align="center">
|
|
9
|
-
<img src="assets/preview.gif" alt="
|
|
14
|
+
<img src="assets/preview.gif" alt="WebGPU fluid simulation — fluid text and image effects in React" height="300px"/>
|
|
10
15
|
</p>
|
|
11
16
|
<p align="center"><strong>Live demo →</strong></p>
|
|
12
17
|
</a>
|
|
13
18
|
|
|
14
19
|
```bash
|
|
15
|
-
|
|
16
|
-
#
|
|
20
|
+
npm install @jayf0x/fluidity-js
|
|
21
|
+
# bun add / yarn add / pnpm add
|
|
17
22
|
```
|
|
18
23
|
|
|
19
|
-
> **WebGPU-first**
|
|
24
|
+
> **WebGPU-first** — falls back automatically to WebGL2 → WebGL1. No configuration required.
|
|
20
25
|
|
|
21
26
|
---
|
|
22
27
|
|
|
23
|
-
##
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- **Fluid text effects** — wrap fluid around text as obstacle + background
|
|
31
|
+
- **Fluid image effects** — apply fluid over any bitmap with `cover`/`contain` sizing
|
|
32
|
+
- **5 visual algorithms** — standard, glass, ink, aurora, ripple
|
|
33
|
+
- **5 presets** — calm, sand, wave, neon, smoke
|
|
34
|
+
- **Web Worker** — simulation runs off the main thread via OffscreenCanvas
|
|
35
|
+
- **WebGPU + WebGL2 + WebGL1** — widest browser compatibility
|
|
36
|
+
- **Fully typed** — ambient TypeScript types, no import needed
|
|
37
|
+
- **React 17+** — forwardRef components, StrictMode safe
|
|
38
|
+
- **Programmatic API** — `splat()`, `move()`, `updateConfig()`, `reset()`
|
|
39
|
+
- **Reactive props** — change algorithm, preset, quality, config at runtime
|
|
40
|
+
|
|
41
|
+
---
|
|
24
42
|
|
|
25
|
-
|
|
43
|
+
## Quickstart
|
|
26
44
|
|
|
27
45
|
```tsx
|
|
28
|
-
import { FluidText } from '@jayf0x/fluidity-js';
|
|
46
|
+
import { FluidText, FluidImage } from '@jayf0x/fluidity-js';
|
|
29
47
|
|
|
48
|
+
// Fluid text — reacts to cursor movement
|
|
30
49
|
export function Hero() {
|
|
31
50
|
return (
|
|
32
51
|
<div style={{ width: '100%', height: 400 }}>
|
|
@@ -34,13 +53,8 @@ export function Hero() {
|
|
|
34
53
|
</div>
|
|
35
54
|
);
|
|
36
55
|
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
**Full-bleed image cover — one line to make it alive:**
|
|
40
|
-
|
|
41
|
-
```tsx
|
|
42
|
-
import { FluidImage } from '@jayf0x/fluidity-js';
|
|
43
56
|
|
|
57
|
+
// Full-bleed fluid image
|
|
44
58
|
export function Cover() {
|
|
45
59
|
return (
|
|
46
60
|
<div style={{ width: '100%', height: '100vh' }}>
|
|
@@ -50,7 +64,7 @@ export function Cover() {
|
|
|
50
64
|
}
|
|
51
65
|
```
|
|
52
66
|
|
|
53
|
-
**
|
|
67
|
+
**Presets and algorithms:**
|
|
54
68
|
|
|
55
69
|
```tsx
|
|
56
70
|
<FluidText text="Wicked" preset="neon" algorithm="glass" />
|
|
@@ -58,7 +72,11 @@ export function Cover() {
|
|
|
58
72
|
<FluidText text="Chill" preset="calm" quality={{ dpr: 1, sim: 0.75 }} />
|
|
59
73
|
```
|
|
60
74
|
|
|
61
|
-
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Programmatic control
|
|
78
|
+
|
|
79
|
+
Use `ref` to trigger effects from code — attractors, scroll-driven splats, click bursts:
|
|
62
80
|
|
|
63
81
|
```tsx
|
|
64
82
|
import { useRef } from 'react';
|
|
@@ -80,7 +98,14 @@ export function Interactive() {
|
|
|
80
98
|
}
|
|
81
99
|
```
|
|
82
100
|
|
|
83
|
-
|
|
101
|
+
### FluidHandle methods
|
|
102
|
+
|
|
103
|
+
| Method | Description |
|
|
104
|
+
|--------|-------------|
|
|
105
|
+
| `reset()` | Re-initialise simulation and reload source |
|
|
106
|
+
| `updateConfig(patch)` | Merge a partial config update into the running sim |
|
|
107
|
+
| `move({ x, y, strength? })` | Programmatic pointer input (canvas-relative px) |
|
|
108
|
+
| `splat(x, y, vx, vy, strength?)` | Inject a fluid splat directly — safe to call N×/frame |
|
|
84
109
|
|
|
85
110
|
---
|
|
86
111
|
|
|
@@ -88,50 +113,50 @@ Official examples → [`demo/src/examples/`](./demo/src/examples/)
|
|
|
88
113
|
|
|
89
114
|
### FluidText
|
|
90
115
|
|
|
91
|
-
| Prop
|
|
92
|
-
|
|
93
|
-
| `text`
|
|
94
|
-
| `fontSize`
|
|
95
|
-
| `color`
|
|
96
|
-
| `fontFamily` | `string`
|
|
97
|
-
| `fontWeight` | `string \| number` | `900`
|
|
116
|
+
| Prop | Type | Default |
|
|
117
|
+
|------|------|---------|
|
|
118
|
+
| `text` | `string` | — |
|
|
119
|
+
| `fontSize` | `number` | `100` |
|
|
120
|
+
| `color` | `string` | `'#ffffff'` |
|
|
121
|
+
| `fontFamily` | `string` | `'sans-serif'` |
|
|
122
|
+
| `fontWeight` | `string \| number` | `900` |
|
|
98
123
|
|
|
99
124
|
### FluidImage
|
|
100
125
|
|
|
101
|
-
| Prop
|
|
102
|
-
|
|
103
|
-
| `src`
|
|
126
|
+
| Prop | Type | Default |
|
|
127
|
+
|------|------|---------|
|
|
128
|
+
| `src` | `string` | — |
|
|
104
129
|
| `imageSize` | `string \| number` | `'cover'` |
|
|
105
|
-
| `effect`
|
|
130
|
+
| `effect` | `number` | `0` |
|
|
106
131
|
|
|
107
132
|
### Shared props
|
|
108
133
|
|
|
109
|
-
| Prop
|
|
110
|
-
|
|
111
|
-
| `config`
|
|
112
|
-
| `preset`
|
|
113
|
-
| `algorithm`
|
|
114
|
-
| `quality`
|
|
115
|
-
| `backgroundColor` | `string`
|
|
116
|
-
| `backgroundSrc`
|
|
117
|
-
| `backgroundSize`
|
|
118
|
-
| `isMouseEnabled`
|
|
119
|
-
| `isWorkerEnabled` | `boolean`
|
|
120
|
-
| `useWebGPU`
|
|
121
|
-
| `className`
|
|
122
|
-
| `style`
|
|
134
|
+
| Prop | Type | Default |
|
|
135
|
+
|------|------|---------|
|
|
136
|
+
| `config` | `Partial<FluidConfig>` | — |
|
|
137
|
+
| `preset` | `PresetKey` | — |
|
|
138
|
+
| `algorithm` | `FluidAlgorithm` | `'standard'` |
|
|
139
|
+
| `quality` | `FluidQuality` | `{ dpr: 1, sim: 0.5 }` |
|
|
140
|
+
| `backgroundColor` | `string` | `'#0a0a0a'` |
|
|
141
|
+
| `backgroundSrc` | `string` | — |
|
|
142
|
+
| `backgroundSize` | `string \| number` | `'cover'` |
|
|
143
|
+
| `isMouseEnabled` | `boolean` | `true` |
|
|
144
|
+
| `isWorkerEnabled` | `boolean` | `true` |
|
|
145
|
+
| `useWebGPU` | `boolean` | `true` |
|
|
146
|
+
| `className` | `string` | — |
|
|
147
|
+
| `style` | `CSSProperties` | — |
|
|
123
148
|
|
|
124
149
|
---
|
|
125
150
|
|
|
126
151
|
## Algorithms
|
|
127
152
|
|
|
128
|
-
| Value
|
|
129
|
-
|
|
130
|
-
| `'standard'` | Colour overlay + gentle refraction (default)
|
|
131
|
-
| `'glass'`
|
|
132
|
-
| `'ink'`
|
|
133
|
-
| `'aurora'`
|
|
134
|
-
| `'ripple'`
|
|
153
|
+
| Value | Character |
|
|
154
|
+
|-------|-----------|
|
|
155
|
+
| `'standard'` | Colour overlay + gentle refraction (default) |
|
|
156
|
+
| `'glass'` | UV distortion only — bent-glass, no colour |
|
|
157
|
+
| `'ink'` | Dense opaque pigment that accumulates and stains |
|
|
158
|
+
| `'aurora'` | Velocity-field UV warp — liquid metal / lava-lamp |
|
|
159
|
+
| `'ripple'` | Exaggerated normals + Fresnel rim — still water |
|
|
135
160
|
|
|
136
161
|
```tsx
|
|
137
162
|
<FluidImage src="/photo.jpg" algorithm="aurora" />
|
|
@@ -140,74 +165,125 @@ Official examples → [`demo/src/examples/`](./demo/src/examples/)
|
|
|
140
165
|
|
|
141
166
|
---
|
|
142
167
|
|
|
143
|
-
##
|
|
144
|
-
|
|
145
|
-
`quality` controls rendering resolution on two independent axes. Both props are reactive — you can adjust them at runtime.
|
|
146
|
-
|
|
147
|
-
| Field | Range | Default | Description |
|
|
148
|
-
| ----- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
149
|
-
| `dpr` | 0.1 – 1 | `1` | Canvas backing resolution as a fraction of `devicePixelRatio`. `0.5` on a Retina screen renders at 1× instead of 2×, saving ~75% fill rate. |
|
|
150
|
-
| `sim` | 0.1 – 1 | `0.5` | Simulation FBO size as a fraction of canvas size. Lower = cheaper GPU, less fluid detail. |
|
|
168
|
+
## Presets
|
|
151
169
|
|
|
152
170
|
```tsx
|
|
153
|
-
<FluidText text="
|
|
171
|
+
<FluidText text="Wicked" preset="neon" />
|
|
172
|
+
<FluidText text="Calm vibes" preset="calm" />
|
|
154
173
|
```
|
|
155
174
|
|
|
156
|
-
`
|
|
175
|
+
Available: `calm` · `sand` · `wave` · `neon` · `smoke`
|
|
176
|
+
|
|
177
|
+
Presets are reactive — changing the prop at runtime re-applies the config. Any `config` values you pass override the preset. The `algorithm` prop also overrides the preset's algorithm.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Quality
|
|
182
|
+
|
|
183
|
+
`quality` controls rendering resolution on two independent axes. Both are reactive at runtime.
|
|
184
|
+
|
|
185
|
+
| Field | Range | Default | Description |
|
|
186
|
+
|-------|-------|---------|-------------|
|
|
187
|
+
| `dpr` | 0.1–1 | `1` | Canvas backing resolution as fraction of `devicePixelRatio`. `0.5` on Retina saves ~75% fill rate. |
|
|
188
|
+
| `sim` | 0.1–1 | `0.5` | Simulation FBO size as fraction of canvas size. Lower = cheaper GPU, less fluid detail. |
|
|
157
189
|
|
|
158
190
|
```tsx
|
|
159
|
-
// Sharp
|
|
191
|
+
// Sharp canvas, cheap simulation
|
|
160
192
|
<FluidImage src="/hero.jpg" quality={{ dpr: 1, sim: 0.2 }} />
|
|
161
193
|
|
|
162
|
-
// Lower
|
|
194
|
+
// Lower canvas res, full simulation quality
|
|
163
195
|
<FluidImage src="/hero.jpg" quality={{ dpr: 0.5, sim: 1 }} />
|
|
164
196
|
```
|
|
165
197
|
|
|
166
198
|
---
|
|
167
199
|
|
|
168
|
-
## FluidConfig
|
|
169
|
-
|
|
170
|
-
| Key
|
|
171
|
-
|
|
172
|
-
| `densityDissipation`
|
|
173
|
-
| `velocityDissipation` | `0.93`
|
|
174
|
-
| `pressureIterations`
|
|
175
|
-
| `curl`
|
|
176
|
-
| `splatRadius`
|
|
177
|
-
| `splatForce`
|
|
178
|
-
| `refraction`
|
|
179
|
-
| `specularExp`
|
|
180
|
-
| `shine`
|
|
181
|
-
| `waterColor`
|
|
182
|
-
| `glowColor`
|
|
183
|
-
| `warpStrength`
|
|
200
|
+
## FluidConfig reference
|
|
201
|
+
|
|
202
|
+
| Key | Default | Description |
|
|
203
|
+
|-----|---------|-------------|
|
|
204
|
+
| `densityDissipation` | `0.992` | How long ink lingers (0–1) |
|
|
205
|
+
| `velocityDissipation` | `0.93` | How fast velocity decays (0–1) |
|
|
206
|
+
| `pressureIterations` | `1` | Jacobi iterations — accuracy vs. cost |
|
|
207
|
+
| `curl` | `0.0001` | Vorticity / swirl. `0.2`–`0.5` for visible eddies |
|
|
208
|
+
| `splatRadius` | `0.004` | Brush radius |
|
|
209
|
+
| `splatForce` | `0.91` | Force applied by brush |
|
|
210
|
+
| `refraction` | `0.25` | Background warp strength |
|
|
211
|
+
| `specularExp` | `1.01` | Specular highlight sharpness |
|
|
212
|
+
| `shine` | `0.01` | Highlight intensity |
|
|
213
|
+
| `waterColor` | `[0, 0, 0]` | Base fluid colour `[R, G, B]` (0–1) |
|
|
214
|
+
| `glowColor` | `[0.7, 0.85, 1.0]` | Glow / specular colour `[R, G, B]` (0–1) |
|
|
215
|
+
| `warpStrength` | `0.015` | UV warp intensity (`aurora` algorithm) |
|
|
184
216
|
|
|
185
217
|
---
|
|
186
218
|
|
|
187
|
-
##
|
|
219
|
+
## TypeScript
|
|
188
220
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
221
|
+
All types are globally ambient — no import required:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
// Available globally after installing the package:
|
|
225
|
+
// FluidConfig, FluidHandle, FluidAlgorithm, FluidQuality, PresetKey
|
|
226
|
+
```
|
|
195
227
|
|
|
196
228
|
---
|
|
197
229
|
|
|
198
|
-
##
|
|
230
|
+
## How it works
|
|
199
231
|
|
|
200
|
-
|
|
201
|
-
<FluidText text="Wicked" preset="neon" />
|
|
202
|
-
<FluidText text="Wicked" preset="calm" />
|
|
203
|
-
```
|
|
232
|
+
fluidity-js runs a real-time **Navier-Stokes fluid simulation** entirely on the GPU:
|
|
204
233
|
|
|
205
|
-
|
|
234
|
+
1. **Advect velocity** — move velocity field along itself; obstacle mask zeroes inside text/image
|
|
235
|
+
2. **Advect density** — move ink/colour field with velocity
|
|
236
|
+
3. **Curl → vorticity confinement** — preserve swirling detail
|
|
237
|
+
4. **Splat** — inject velocity + density at cursor or programmatic positions
|
|
238
|
+
5. **Divergence → pressure solve** — N Jacobi iterations for incompressibility
|
|
239
|
+
6. **Gradient subtract** — enforce divergence-free velocity
|
|
240
|
+
7. **Display pass** — 5 texture units: density, obstacle, background, coverage mask, velocity; 5 algorithms selectable by uniform
|
|
241
|
+
|
|
242
|
+
The simulation runs in a **Web Worker** by default, communicating with the React component via `postMessage`. The canvas is transferred to the worker via `OffscreenCanvas.transferControlToOffscreen()` for true off-thread rendering.
|
|
243
|
+
|
|
244
|
+
**WebGPU path** uses render pipelines via `@webgpu/types`. **WebGL path** uses double-framebuffer objects (ping-pong FBOs) with GLSL shaders.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Browser support
|
|
249
|
+
|
|
250
|
+
| Browser | WebGPU | WebGL2 | WebGL1 |
|
|
251
|
+
|---------|--------|--------|--------|
|
|
252
|
+
| Chrome 113+ | ✅ | ✅ | ✅ |
|
|
253
|
+
| Edge 113+ | ✅ | ✅ | ✅ |
|
|
254
|
+
| Firefox | — | ✅ | ✅ |
|
|
255
|
+
| Safari 17+ | ✅ (partial) | ✅ | ✅ |
|
|
256
|
+
| Safari < 17 | — | ✅ | ✅ |
|
|
257
|
+
| Mobile Chrome | — | ✅ | ✅ |
|
|
258
|
+
|
|
259
|
+
Fallback is automatic — no configuration needed.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Examples
|
|
264
|
+
|
|
265
|
+
Full working examples live in [`demo/src/examples/`](./demo/src/examples/):
|
|
266
|
+
|
|
267
|
+
- `TextExample.tsx` — FluidText with Leva controls
|
|
268
|
+
- `ImageExample.tsx` — FluidImage with backgroundSrc
|
|
269
|
+
- `SplashExample.tsx` — full-page fluid hero
|
|
270
|
+
- `SplitExample.tsx` — split-screen comparison
|
|
271
|
+
- `PresetsExample.tsx` — all presets side by side
|
|
272
|
+
|
|
273
|
+
[**Open live demo →**](https://jayf0x.github.io/fluidity)
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Contributing
|
|
278
|
+
|
|
279
|
+
Issues and PRs welcome. See [AGENTS.md](./AGENTS.md) for agent-specific instructions and code conventions.
|
|
206
280
|
|
|
207
|
-
|
|
281
|
+
1. Fork → branch → PR against `main`
|
|
282
|
+
2. Run `bun test:claude` before pushing — all 83 tests must pass
|
|
283
|
+
3. No new dependencies without discussion
|
|
208
284
|
|
|
209
285
|
---
|
|
210
286
|
|
|
211
287
|
## License
|
|
212
288
|
|
|
213
|
-
[MIT](./LICENSE)
|
|
289
|
+
[MIT](./LICENSE) © [jayf0x](https://github.com/jayf0x)
|
package/dist/globals.d.ts
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
type FluidAlgorithm = 'standard' | 'glass' | 'ink' | 'aurora' | 'ripple';
|
|
4
4
|
|
|
5
|
+
/** RGB tuple (values 0–1) or a CSS hex string (#RGB, #RRGGBB, #RRGGBBAA — alpha stripped). */
|
|
6
|
+
type FluidColor = [number, number, number] | `#${string}`;
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* Granular performance/quality controls. Both axes are independent — you can
|
|
7
10
|
* run a sharp display at a coarser simulation, or vice versa.
|
|
@@ -24,8 +27,8 @@ interface FluidConfig {
|
|
|
24
27
|
refraction: number;
|
|
25
28
|
specularExp: number;
|
|
26
29
|
shine: number;
|
|
27
|
-
waterColor:
|
|
28
|
-
glowColor:
|
|
30
|
+
waterColor: FluidColor;
|
|
31
|
+
glowColor: FluidColor;
|
|
29
32
|
algorithm: FluidAlgorithm;
|
|
30
33
|
warpStrength: number;
|
|
31
34
|
}
|
|
@@ -55,6 +58,8 @@ interface FluidBaseProps {
|
|
|
55
58
|
backgroundSize?: string | number;
|
|
56
59
|
/** enabled greater performance, but not every browser supports it */
|
|
57
60
|
useWebGPU?: boolean;
|
|
61
|
+
/** Enable transparent canvas (default true). Set false for a minor perf gain when transparency is not needed. */
|
|
62
|
+
enableAlpha?: boolean;
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
interface FluidTextProps extends FluidBaseProps {
|