@jayf0x/fluidity-js 0.2.2 → 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 +171 -96
- 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)
|
|
6
9
|
|
|
7
|
-
|
|
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.**
|
|
11
|
+
|
|
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,11 +72,14 @@ 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';
|
|
65
|
-
|
|
66
83
|
import { FluidText } from '@jayf0x/fluidity-js';
|
|
67
84
|
|
|
68
85
|
export function Interactive() {
|
|
@@ -81,7 +98,14 @@ export function Interactive() {
|
|
|
81
98
|
}
|
|
82
99
|
```
|
|
83
100
|
|
|
84
|
-
|
|
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 |
|
|
85
109
|
|
|
86
110
|
---
|
|
87
111
|
|
|
@@ -89,50 +113,50 @@ Official examples → [`demo/src/examples/`](./demo/src/examples/)
|
|
|
89
113
|
|
|
90
114
|
### FluidText
|
|
91
115
|
|
|
92
|
-
| Prop
|
|
93
|
-
|
|
94
|
-
| `text`
|
|
95
|
-
| `fontSize`
|
|
96
|
-
| `color`
|
|
97
|
-
| `fontFamily` | `string`
|
|
98
|
-
| `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` |
|
|
99
123
|
|
|
100
124
|
### FluidImage
|
|
101
125
|
|
|
102
|
-
| Prop
|
|
103
|
-
|
|
104
|
-
| `src`
|
|
126
|
+
| Prop | Type | Default |
|
|
127
|
+
|------|------|---------|
|
|
128
|
+
| `src` | `string` | — |
|
|
105
129
|
| `imageSize` | `string \| number` | `'cover'` |
|
|
106
|
-
| `effect`
|
|
130
|
+
| `effect` | `number` | `0` |
|
|
107
131
|
|
|
108
132
|
### Shared props
|
|
109
133
|
|
|
110
|
-
| Prop
|
|
111
|
-
|
|
112
|
-
| `config`
|
|
113
|
-
| `preset`
|
|
114
|
-
| `algorithm`
|
|
115
|
-
| `quality`
|
|
116
|
-
| `backgroundColor` | `string`
|
|
117
|
-
| `backgroundSrc`
|
|
118
|
-
| `backgroundSize`
|
|
119
|
-
| `isMouseEnabled`
|
|
120
|
-
| `isWorkerEnabled` | `boolean`
|
|
121
|
-
| `useWebGPU`
|
|
122
|
-
| `className`
|
|
123
|
-
| `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` | — |
|
|
124
148
|
|
|
125
149
|
---
|
|
126
150
|
|
|
127
151
|
## Algorithms
|
|
128
152
|
|
|
129
|
-
| Value
|
|
130
|
-
|
|
131
|
-
| `'standard'` | Colour overlay + gentle refraction (default)
|
|
132
|
-
| `'glass'`
|
|
133
|
-
| `'ink'`
|
|
134
|
-
| `'aurora'`
|
|
135
|
-
| `'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 |
|
|
136
160
|
|
|
137
161
|
```tsx
|
|
138
162
|
<FluidImage src="/photo.jpg" algorithm="aurora" />
|
|
@@ -141,74 +165,125 @@ Official examples → [`demo/src/examples/`](./demo/src/examples/)
|
|
|
141
165
|
|
|
142
166
|
---
|
|
143
167
|
|
|
144
|
-
##
|
|
145
|
-
|
|
146
|
-
`quality` controls rendering resolution on two independent axes. Both props are reactive — you can adjust them at runtime.
|
|
147
|
-
|
|
148
|
-
| Field | Range | Default | Description |
|
|
149
|
-
| ----- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
150
|
-
| `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. |
|
|
151
|
-
| `sim` | 0.1 – 1 | `0.5` | Simulation FBO size as a fraction of canvas size. Lower = cheaper GPU, less fluid detail. |
|
|
168
|
+
## Presets
|
|
152
169
|
|
|
153
170
|
```tsx
|
|
154
|
-
<FluidText text="
|
|
171
|
+
<FluidText text="Wicked" preset="neon" />
|
|
172
|
+
<FluidText text="Calm vibes" preset="calm" />
|
|
155
173
|
```
|
|
156
174
|
|
|
157
|
-
`
|
|
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. |
|
|
158
189
|
|
|
159
190
|
```tsx
|
|
160
|
-
// Sharp
|
|
191
|
+
// Sharp canvas, cheap simulation
|
|
161
192
|
<FluidImage src="/hero.jpg" quality={{ dpr: 1, sim: 0.2 }} />
|
|
162
193
|
|
|
163
|
-
// Lower
|
|
194
|
+
// Lower canvas res, full simulation quality
|
|
164
195
|
<FluidImage src="/hero.jpg" quality={{ dpr: 0.5, sim: 1 }} />
|
|
165
196
|
```
|
|
166
197
|
|
|
167
198
|
---
|
|
168
199
|
|
|
169
|
-
## FluidConfig
|
|
170
|
-
|
|
171
|
-
| Key
|
|
172
|
-
|
|
173
|
-
| `densityDissipation`
|
|
174
|
-
| `velocityDissipation` | `0.93`
|
|
175
|
-
| `pressureIterations`
|
|
176
|
-
| `curl`
|
|
177
|
-
| `splatRadius`
|
|
178
|
-
| `splatForce`
|
|
179
|
-
| `refraction`
|
|
180
|
-
| `specularExp`
|
|
181
|
-
| `shine`
|
|
182
|
-
| `waterColor`
|
|
183
|
-
| `glowColor`
|
|
184
|
-
| `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) |
|
|
185
216
|
|
|
186
217
|
---
|
|
187
218
|
|
|
188
|
-
##
|
|
219
|
+
## TypeScript
|
|
189
220
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
+
```
|
|
196
227
|
|
|
197
228
|
---
|
|
198
229
|
|
|
199
|
-
##
|
|
230
|
+
## How it works
|
|
200
231
|
|
|
201
|
-
|
|
202
|
-
<FluidText text="Wicked" preset="neon" />
|
|
203
|
-
<FluidText text="Wicked" preset="calm" />
|
|
204
|
-
```
|
|
232
|
+
fluidity-js runs a real-time **Navier-Stokes fluid simulation** entirely on the GPU:
|
|
205
233
|
|
|
206
|
-
|
|
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.
|
|
207
280
|
|
|
208
|
-
|
|
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
|
|
209
284
|
|
|
210
285
|
---
|
|
211
286
|
|
|
212
287
|
## License
|
|
213
288
|
|
|
214
|
-
[MIT](./LICENSE)
|
|
289
|
+
[MIT](./LICENSE) © [jayf0x](https://github.com/jayf0x)
|
package/llms.txt
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# fluidity-js
|
|
2
|
+
|
|
3
|
+
> WebGPU-first fluid simulation library for React. Real-time Navier-Stokes solver — text and image effects with hardware-accelerated fluid dynamics.
|
|
4
|
+
|
|
5
|
+
- npm: https://www.npmjs.com/package/@jayf0x/fluidity-js
|
|
6
|
+
- GitHub: https://github.com/jayf0x/fluidity
|
|
7
|
+
- Live demo: https://jayf0x.github.io/fluidity
|
|
8
|
+
- License: MIT
|
|
9
|
+
- Version: 0.2.2
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @jayf0x/fluidity-js
|
|
15
|
+
# bun add / yarn add / pnpm add also work
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Peer deps: react >=17, react-dom >=17
|
|
19
|
+
|
|
20
|
+
## Core concepts
|
|
21
|
+
|
|
22
|
+
- **WebGPU-first** with automatic fallback to WebGL2 → WebGL1
|
|
23
|
+
- Runs simulation in a **Web Worker** via OffscreenCanvas (configurable)
|
|
24
|
+
- Two render modes: `FluidText` (text as obstacle) and `FluidImage` (bitmap as obstacle)
|
|
25
|
+
- Navier-Stokes pipeline: advect velocity → advect density → curl/vorticity → splat → divergence → pressure solve → gradient subtract → display
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { FluidText, FluidImage } from '@jayf0x/fluidity-js';
|
|
31
|
+
|
|
32
|
+
// Fluid text that reacts to cursor
|
|
33
|
+
<FluidText text="Hello World" fontSize={140} color="#ffffff" />
|
|
34
|
+
|
|
35
|
+
// Fluid image with aurora algorithm
|
|
36
|
+
<FluidImage src="/hero.jpg" algorithm="aurora" />
|
|
37
|
+
|
|
38
|
+
// With preset and custom config
|
|
39
|
+
<FluidText text="Wicked" preset="neon" algorithm="glass" config={{ curl: 0.4 }} />
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## FluidText props
|
|
43
|
+
|
|
44
|
+
| Prop | Type | Default |
|
|
45
|
+
|------|------|---------|
|
|
46
|
+
| text | string | required |
|
|
47
|
+
| fontSize | number | 100 |
|
|
48
|
+
| color | string | '#ffffff' |
|
|
49
|
+
| fontFamily | string | 'sans-serif' |
|
|
50
|
+
| fontWeight | string\|number | 900 |
|
|
51
|
+
|
|
52
|
+
## FluidImage props
|
|
53
|
+
|
|
54
|
+
| Prop | Type | Default |
|
|
55
|
+
|------|------|---------|
|
|
56
|
+
| src | string | required |
|
|
57
|
+
| imageSize | string\|number | 'cover' |
|
|
58
|
+
| effect | number | 0 |
|
|
59
|
+
|
|
60
|
+
## Shared props (both components)
|
|
61
|
+
|
|
62
|
+
| Prop | Type | Default |
|
|
63
|
+
|------|------|---------|
|
|
64
|
+
| config | Partial<FluidConfig> | — |
|
|
65
|
+
| preset | PresetKey | — |
|
|
66
|
+
| algorithm | FluidAlgorithm | 'standard' |
|
|
67
|
+
| quality | FluidQuality | { dpr: 1, sim: 0.5 } |
|
|
68
|
+
| backgroundColor | string | '#0a0a0a' |
|
|
69
|
+
| backgroundSrc | string | — |
|
|
70
|
+
| backgroundSize | string\|number | 'cover' |
|
|
71
|
+
| isMouseEnabled | boolean | true |
|
|
72
|
+
| isWorkerEnabled | boolean | true |
|
|
73
|
+
| useWebGPU | boolean | true |
|
|
74
|
+
| className | string | — |
|
|
75
|
+
| style | CSSProperties | — |
|
|
76
|
+
|
|
77
|
+
## FluidHandle ref API
|
|
78
|
+
|
|
79
|
+
Access via `useRef<FluidHandle>()` and `ref` prop:
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
const fluid = useRef<FluidHandle>(null);
|
|
83
|
+
<FluidText ref={fluid} text="Touch me" />
|
|
84
|
+
fluid.current?.splat(200, 200, 8, -4);
|
|
85
|
+
fluid.current?.updateConfig({ curl: 0.5 });
|
|
86
|
+
fluid.current?.reset();
|
|
87
|
+
fluid.current?.move({ x: 100, y: 100, strength: 2 });
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Algorithms
|
|
91
|
+
|
|
92
|
+
| Value | Effect |
|
|
93
|
+
|-------|--------|
|
|
94
|
+
| standard | Colour overlay + gentle refraction |
|
|
95
|
+
| glass | UV distortion only — bent glass |
|
|
96
|
+
| ink | Dense opaque pigment that accumulates |
|
|
97
|
+
| aurora | Velocity-field UV warp — liquid metal |
|
|
98
|
+
| ripple | Exaggerated normals + Fresnel rim |
|
|
99
|
+
|
|
100
|
+
## Presets
|
|
101
|
+
|
|
102
|
+
`calm` · `sand` · `wave` · `neon` · `smoke`
|
|
103
|
+
|
|
104
|
+
## FluidConfig options
|
|
105
|
+
|
|
106
|
+
| Key | Default | Description |
|
|
107
|
+
|-----|---------|-------------|
|
|
108
|
+
| densityDissipation | 0.992 | Ink persistence (0–1) |
|
|
109
|
+
| velocityDissipation | 0.93 | Velocity decay (0–1) |
|
|
110
|
+
| pressureIterations | 1 | Jacobi iterations |
|
|
111
|
+
| curl | 0.0001 | Vorticity / swirl strength |
|
|
112
|
+
| splatRadius | 0.004 | Brush radius |
|
|
113
|
+
| splatForce | 0.91 | Brush force |
|
|
114
|
+
| refraction | 0.25 | Background warp strength |
|
|
115
|
+
| specularExp | 1.01 | Specular sharpness |
|
|
116
|
+
| shine | 0.01 | Highlight intensity |
|
|
117
|
+
| waterColor | [0,0,0] | Base fluid RGB (0–1 each) |
|
|
118
|
+
| glowColor | [0.7,0.85,1.0] | Specular/glow RGB (0–1 each) |
|
|
119
|
+
| warpStrength | 0.015 | UV warp (aurora algorithm) |
|
|
120
|
+
|
|
121
|
+
## Quality control
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
// dpr: canvas backing resolution fraction (0.1–1)
|
|
125
|
+
// sim: simulation FBO size fraction (0.1–1)
|
|
126
|
+
<FluidText text="hello" quality={{ dpr: 0.75, sim: 0.25 }} />
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Both axes are independent and reactive at runtime.
|
|
130
|
+
|
|
131
|
+
## TypeScript types
|
|
132
|
+
|
|
133
|
+
All types are globally ambient — no import needed:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
// Available globally after installing the package:
|
|
137
|
+
FluidConfig
|
|
138
|
+
FluidHandle
|
|
139
|
+
FluidAlgorithm // 'standard'|'glass'|'ink'|'aurora'|'ripple'
|
|
140
|
+
FluidQuality // { dpr: number; sim: number }
|
|
141
|
+
PresetKey // 'calm'|'sand'|'wave'|'neon'|'smoke'
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Architecture summary
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
React component (FluidText / FluidImage)
|
|
148
|
+
└── useFluid hook
|
|
149
|
+
└── FluidController
|
|
150
|
+
├── [worker mode] Web Worker → FluidSimulation (OffscreenCanvas)
|
|
151
|
+
└── [main mode] FluidSimulation directly
|
|
152
|
+
└── WebGPU path (FluidSimulation.create())
|
|
153
|
+
└── WebGL2 / WebGL1 fallback
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Worker transfer: zero-copy OffscreenCanvas transfer. Background images transferred as ImageBitmap (zero-copy). Safe to destroy/recreate under React StrictMode.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jayf0x/fluidity-js",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "WebGPU-first real-time Navier-Stokes fluid simulation for React — interactive water, ink, glass, aurora, and ripple effects on text and images. Falls back to WebGL2/WebGL1. Runs in a Web Worker.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./dist/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
"types": "./src/index.d.ts",
|
|
14
14
|
"files": [
|
|
15
15
|
"dist",
|
|
16
|
-
"README.md"
|
|
16
|
+
"README.md",
|
|
17
|
+
"AGENTS.md",
|
|
18
|
+
"llms.txt",
|
|
19
|
+
"CONTRIBUTING.md"
|
|
17
20
|
],
|
|
18
21
|
"sideEffects": false,
|
|
19
22
|
"scripts": {
|
|
@@ -30,12 +33,37 @@
|
|
|
30
33
|
"publish:npm": "bash ./scripts/publish-npm.sh"
|
|
31
34
|
},
|
|
32
35
|
"keywords": [
|
|
36
|
+
"webgpu",
|
|
33
37
|
"webgl",
|
|
38
|
+
"webgl2",
|
|
34
39
|
"fluid",
|
|
35
|
-
"simulation",
|
|
40
|
+
"fluid-simulation",
|
|
41
|
+
"fluid-dynamics",
|
|
42
|
+
"navier-stokes",
|
|
36
43
|
"react",
|
|
37
44
|
"animation",
|
|
38
|
-
"canvas"
|
|
45
|
+
"canvas",
|
|
46
|
+
"water-effect",
|
|
47
|
+
"ink-effect",
|
|
48
|
+
"glass-effect",
|
|
49
|
+
"aurora-effect",
|
|
50
|
+
"ripple-effect",
|
|
51
|
+
"interactive-animation",
|
|
52
|
+
"canvas-animation",
|
|
53
|
+
"hover-effect",
|
|
54
|
+
"text-animation",
|
|
55
|
+
"image-effect",
|
|
56
|
+
"offscreencanvas",
|
|
57
|
+
"web-worker",
|
|
58
|
+
"typescript",
|
|
59
|
+
"react-component",
|
|
60
|
+
"vorticity",
|
|
61
|
+
"gpu",
|
|
62
|
+
"shader",
|
|
63
|
+
"glsl",
|
|
64
|
+
"wgsl",
|
|
65
|
+
"visual-effect",
|
|
66
|
+
"creative-coding"
|
|
39
67
|
],
|
|
40
68
|
"author": "https://github.com/jayf0x",
|
|
41
69
|
"license": "MIT",
|
|
@@ -46,7 +74,7 @@
|
|
|
46
74
|
"bugs": {
|
|
47
75
|
"url": "https://github.com/jayf0x/fluidity/issues"
|
|
48
76
|
},
|
|
49
|
-
"homepage": "https://github.
|
|
77
|
+
"homepage": "https://jayf0x.github.io/fluidity",
|
|
50
78
|
"peerDependencies": {
|
|
51
79
|
"react": ">=17.0.0",
|
|
52
80
|
"react-dom": ">=17.0.0"
|