@jayf0x/fluidity-js 0.2.3 → 0.2.4
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/README.md +84 -174
- package/dist/index.js +653 -629
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# fluidity-js —
|
|
1
|
+
# fluidity-js — Upgrade your UX
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@jayf0x/fluidity-js)
|
|
4
4
|
[](https://www.npmjs.com/package/@jayf0x/fluidity-js)
|
|
@@ -7,105 +7,75 @@
|
|
|
7
7
|
[](./tsconfig.json)
|
|
8
8
|
[](https://github.com/jayf0x/fluidity/actions/workflows/ci.yml)
|
|
9
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.**
|
|
11
|
-
|
|
12
10
|
<a href="https://jayf0x.github.io/fluidity">
|
|
13
11
|
<p align="center">
|
|
14
|
-
<img src="assets/preview.gif" alt="
|
|
12
|
+
<img src="assets/preview.gif" alt="Fluid text and image effects in React" height="300px"/>
|
|
15
13
|
</p>
|
|
16
|
-
<p align="center"><strong>
|
|
14
|
+
<p align="center"><strong>Demo & Examples →</strong></p>
|
|
17
15
|
</a>
|
|
18
16
|
|
|
17
|
+
## Quickstart
|
|
18
|
+
|
|
19
19
|
```bash
|
|
20
|
-
|
|
21
|
-
#
|
|
20
|
+
bun add @jayf0x/fluidity-js
|
|
21
|
+
# npm / pnpm / aube
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
> **WebGPU-first** — falls back automatically to WebGL2 → WebGL1. No configuration required.
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
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
|
-
---
|
|
42
|
-
|
|
43
|
-
## Quickstart
|
|
44
|
-
|
|
45
24
|
```tsx
|
|
46
|
-
import {
|
|
25
|
+
import { FluidImage, FluidText } from '@jayf0x/fluidity-js';
|
|
47
26
|
|
|
48
27
|
// Fluid text — reacts to cursor movement
|
|
49
|
-
export
|
|
28
|
+
export const Hero = () => {
|
|
50
29
|
return (
|
|
51
30
|
<div style={{ width: '100%', height: 400 }}>
|
|
52
31
|
<FluidText text="Hello World" fontSize={140} color="#ffffff" />
|
|
53
32
|
</div>
|
|
54
33
|
);
|
|
55
|
-
}
|
|
34
|
+
};
|
|
56
35
|
|
|
57
36
|
// Full-bleed fluid image
|
|
58
|
-
export
|
|
37
|
+
export const Cover = () => {
|
|
59
38
|
return (
|
|
60
39
|
<div style={{ width: '100%', height: '100vh' }}>
|
|
61
40
|
<FluidImage src="/hero.jpg" algorithm="aurora" />
|
|
62
41
|
</div>
|
|
63
42
|
);
|
|
64
|
-
}
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
**Presets and algorithms:**
|
|
68
|
-
|
|
69
|
-
```tsx
|
|
70
|
-
<FluidText text="Wicked" preset="neon" algorithm="glass" />
|
|
71
|
-
<FluidImage src="/poster.jpg" algorithm="ripple" config={{ curl: 0.4, splatRadius: 0.008 }} />
|
|
72
|
-
<FluidText text="Chill" preset="calm" quality={{ dpr: 1, sim: 0.75 }} />
|
|
43
|
+
};
|
|
73
44
|
```
|
|
74
45
|
|
|
75
46
|
---
|
|
76
47
|
|
|
77
48
|
## Programmatic control
|
|
78
49
|
|
|
79
|
-
Use `ref` to trigger effects from code —
|
|
50
|
+
Use `ref` to trigger effects from code — scroll-driven splats, click bursts, attract particles:
|
|
80
51
|
|
|
81
52
|
```tsx
|
|
82
53
|
import { useRef } from 'react';
|
|
54
|
+
|
|
83
55
|
import { FluidText } from '@jayf0x/fluidity-js';
|
|
84
56
|
|
|
85
|
-
export
|
|
57
|
+
export const Interactive = () => {
|
|
86
58
|
const fluid = useRef<FluidHandle>(null);
|
|
87
59
|
|
|
88
60
|
return (
|
|
89
61
|
<>
|
|
90
62
|
<div style={{ width: '100%', height: 400 }}>
|
|
91
|
-
<FluidText ref={fluid} text="
|
|
63
|
+
<FluidText ref={fluid} text="Splash!" fontSize={120} color="#fff" />
|
|
92
64
|
</div>
|
|
93
65
|
<button onClick={() => fluid.current?.splat(200, 200, 8, -4)}>Splat</button>
|
|
94
66
|
<button onClick={() => fluid.current?.updateConfig({ curl: 0.5 })}>Swirl</button>
|
|
95
67
|
<button onClick={() => fluid.current?.reset()}>Reset</button>
|
|
96
68
|
</>
|
|
97
69
|
);
|
|
98
|
-
}
|
|
70
|
+
};
|
|
99
71
|
```
|
|
100
72
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
|
104
|
-
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
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 |
|
|
73
|
+
| Method | What it does |
|
|
74
|
+
| -------------------------------- | --------------------------------------------------------- |
|
|
75
|
+
| `reset()` | Restart the simulation |
|
|
76
|
+
| `updateConfig(patch)` | Change any config value live |
|
|
77
|
+
| `move({ x, y, strength? })` | Simulate a pointer move |
|
|
78
|
+
| `splat(x, y, vx, vy, strength?)` | Inject fluid directly — safe to call many times per frame |
|
|
109
79
|
|
|
110
80
|
---
|
|
111
81
|
|
|
@@ -113,50 +83,50 @@ export function Interactive() {
|
|
|
113
83
|
|
|
114
84
|
### FluidText
|
|
115
85
|
|
|
116
|
-
| Prop
|
|
117
|
-
|
|
118
|
-
| `text`
|
|
119
|
-
| `fontSize`
|
|
120
|
-
| `color`
|
|
121
|
-
| `fontFamily` | `string`
|
|
122
|
-
| `fontWeight` | `string \| number` | `900`
|
|
86
|
+
| Prop | Type | Default |
|
|
87
|
+
| ------------ | ------------------ | -------------- |
|
|
88
|
+
| `text` | `string` | — |
|
|
89
|
+
| `fontSize` | `number` | `100` |
|
|
90
|
+
| `color` | `string` | `'#ffffff'` |
|
|
91
|
+
| `fontFamily` | `string` | `'sans-serif'` |
|
|
92
|
+
| `fontWeight` | `string \| number` | `900` |
|
|
123
93
|
|
|
124
94
|
### FluidImage
|
|
125
95
|
|
|
126
|
-
| Prop
|
|
127
|
-
|
|
128
|
-
| `src`
|
|
96
|
+
| Prop | Type | Default |
|
|
97
|
+
| ----------- | ------------------ | --------- |
|
|
98
|
+
| `src` | `string` | — |
|
|
129
99
|
| `imageSize` | `string \| number` | `'cover'` |
|
|
130
|
-
| `effect`
|
|
100
|
+
| `effect` | `number` | `0` |
|
|
131
101
|
|
|
132
102
|
### Shared props
|
|
133
103
|
|
|
134
|
-
| Prop
|
|
135
|
-
|
|
136
|
-
| `config`
|
|
137
|
-
| `preset`
|
|
138
|
-
| `algorithm`
|
|
139
|
-
| `quality`
|
|
140
|
-
| `backgroundColor` | `string`
|
|
141
|
-
| `backgroundSrc`
|
|
142
|
-
| `backgroundSize`
|
|
143
|
-
| `isMouseEnabled`
|
|
144
|
-
| `isWorkerEnabled` | `boolean`
|
|
145
|
-
| `useWebGPU`
|
|
146
|
-
| `className`
|
|
147
|
-
| `style`
|
|
104
|
+
| Prop | Type | Default |
|
|
105
|
+
| ----------------- | ---------------------- | ---------------------- |
|
|
106
|
+
| `config` | `Partial<FluidConfig>` | — |
|
|
107
|
+
| `preset` | `PresetKey` | — |
|
|
108
|
+
| `algorithm` | `FluidAlgorithm` | `'standard'` |
|
|
109
|
+
| `quality` | `FluidQuality` | `{ dpr: 1, sim: 0.5 }` |
|
|
110
|
+
| `backgroundColor` | `string` | `'#0a0a0a'` |
|
|
111
|
+
| `backgroundSrc` | `string` | — |
|
|
112
|
+
| `backgroundSize` | `string \| number` | `'cover'` |
|
|
113
|
+
| `isMouseEnabled` | `boolean` | `true` |
|
|
114
|
+
| `isWorkerEnabled` | `boolean` | `true` |
|
|
115
|
+
| `useWebGPU` | `boolean` | `true` |
|
|
116
|
+
| `className` | `string` | — |
|
|
117
|
+
| `style` | `CSSProperties` | — |
|
|
148
118
|
|
|
149
119
|
---
|
|
150
120
|
|
|
151
121
|
## Algorithms
|
|
152
122
|
|
|
153
|
-
| Value
|
|
154
|
-
|
|
155
|
-
| `'standard'` | Colour overlay + gentle refraction (default)
|
|
156
|
-
| `'glass'`
|
|
157
|
-
| `'ink'`
|
|
158
|
-
| `'aurora'`
|
|
159
|
-
| `'ripple'`
|
|
123
|
+
| Value | Vibe |
|
|
124
|
+
| ------------ | ------------------------------------------------ |
|
|
125
|
+
| `'standard'` | Colour overlay + gentle refraction (default) |
|
|
126
|
+
| `'glass'` | Bent-glass distortion, no colour |
|
|
127
|
+
| `'ink'` | Dense opaque pigment that accumulates and stains |
|
|
128
|
+
| `'aurora'` | Liquid metal / lava-lamp |
|
|
129
|
+
| `'ripple'` | Still water surface with Fresnel rim |
|
|
160
130
|
|
|
161
131
|
```tsx
|
|
162
132
|
<FluidImage src="/photo.jpg" algorithm="aurora" />
|
|
@@ -165,27 +135,14 @@ export function Interactive() {
|
|
|
165
135
|
|
|
166
136
|
---
|
|
167
137
|
|
|
168
|
-
## Presets
|
|
169
|
-
|
|
170
|
-
```tsx
|
|
171
|
-
<FluidText text="Wicked" preset="neon" />
|
|
172
|
-
<FluidText text="Calm vibes" preset="calm" />
|
|
173
|
-
```
|
|
174
|
-
|
|
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
138
|
## Quality
|
|
182
139
|
|
|
183
|
-
|
|
140
|
+
Control rendering resolution on two independent axes — both reactive at runtime.
|
|
184
141
|
|
|
185
|
-
| Field | Range | Default |
|
|
186
|
-
|
|
187
|
-
| `dpr` | 0.1–1 | `1`
|
|
188
|
-
| `sim` | 0.1–1 | `0.5`
|
|
142
|
+
| Field | Range | Default | What it does |
|
|
143
|
+
| ----- | ----- | ------- | ----------------------------------------------------------------------------------------- |
|
|
144
|
+
| `dpr` | 0.1–1 | `1` | Canvas resolution as fraction of screen pixel ratio. `0.5` on Retina saves ~75% GPU fill. |
|
|
145
|
+
| `sim` | 0.1–1 | `0.5` | Simulation resolution. Lower = cheaper, less detail. |
|
|
189
146
|
|
|
190
147
|
```tsx
|
|
191
148
|
// Sharp canvas, cheap simulation
|
|
@@ -199,88 +156,41 @@ Presets are reactive — changing the prop at runtime re-applies the config. Any
|
|
|
199
156
|
|
|
200
157
|
## FluidConfig reference
|
|
201
158
|
|
|
202
|
-
| Key
|
|
203
|
-
|
|
204
|
-
| `densityDissipation`
|
|
205
|
-
| `velocityDissipation` | `0.93`
|
|
206
|
-
| `pressureIterations`
|
|
207
|
-
| `curl`
|
|
208
|
-
| `splatRadius`
|
|
209
|
-
| `splatForce`
|
|
210
|
-
| `refraction`
|
|
211
|
-
| `specularExp`
|
|
212
|
-
| `shine`
|
|
213
|
-
| `waterColor`
|
|
214
|
-
| `glowColor`
|
|
215
|
-
| `warpStrength`
|
|
216
|
-
|
|
217
|
-
---
|
|
218
|
-
|
|
219
|
-
## TypeScript
|
|
220
|
-
|
|
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
|
-
```
|
|
227
|
-
|
|
228
|
-
---
|
|
229
|
-
|
|
230
|
-
## How it works
|
|
231
|
-
|
|
232
|
-
fluidity-js runs a real-time **Navier-Stokes fluid simulation** entirely on the GPU:
|
|
233
|
-
|
|
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.
|
|
159
|
+
| Key | Default | Description |
|
|
160
|
+
| --------------------- | ------------------ | ----------------------------------------------- |
|
|
161
|
+
| `densityDissipation` | `0.992` | How long ink lingers (0–1) |
|
|
162
|
+
| `velocityDissipation` | `0.93` | How fast fluid slows down (0–1) |
|
|
163
|
+
| `pressureIterations` | `1` | Quality vs. cost trade-off |
|
|
164
|
+
| `curl` | `0.0001` | Swirl intensity. `0.2`–`0.5` for visible eddies |
|
|
165
|
+
| `splatRadius` | `0.004` | Brush radius |
|
|
166
|
+
| `splatForce` | `0.91` | Force applied by brush |
|
|
167
|
+
| `refraction` | `0.25` | Background warp strength |
|
|
168
|
+
| `specularExp` | `1.01` | Specular highlight sharpness |
|
|
169
|
+
| `shine` | `0.01` | Highlight intensity |
|
|
170
|
+
| `waterColor` | `[0, 0, 0]` | Base fluid colour `[R, G, B]` (0–1) |
|
|
171
|
+
| `glowColor` | `[0.7, 0.85, 1.0]` | Glow / specular colour `[R, G, B]` (0–1) |
|
|
172
|
+
| `warpStrength` | `0.015` | UV warp intensity (`aurora` algorithm) |
|
|
245
173
|
|
|
246
174
|
---
|
|
247
175
|
|
|
248
176
|
## Browser support
|
|
249
177
|
|
|
250
|
-
|
|
251
|
-
|---------|--------|--------|--------|
|
|
252
|
-
| Chrome 113+ | ✅ | ✅ | ✅ |
|
|
253
|
-
| Edge 113+ | ✅ | ✅ | ✅ |
|
|
254
|
-
| Firefox | — | ✅ | ✅ |
|
|
255
|
-
| Safari 17+ | ✅ (partial) | ✅ | ✅ |
|
|
256
|
-
| Safari < 17 | — | ✅ | ✅ |
|
|
257
|
-
| Mobile Chrome | — | ✅ | ✅ |
|
|
178
|
+
Works in all modern browsers. Automatically picks the best renderer available — no configuration needed.
|
|
258
179
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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)
|
|
180
|
+
| Browser | Support |
|
|
181
|
+
| ------------- | ------- |
|
|
182
|
+
| Chrome 113+ | ✅ |
|
|
183
|
+
| Edge 113+ | ✅ |
|
|
184
|
+
| Firefox | ✅ |
|
|
185
|
+
| Safari 17+ | ✅ |
|
|
186
|
+
| Safari < 17 | ✅ |
|
|
187
|
+
| Mobile Chrome | ✅ |
|
|
274
188
|
|
|
275
189
|
---
|
|
276
190
|
|
|
277
191
|
## Contributing
|
|
278
192
|
|
|
279
|
-
Issues and PRs welcome. See [
|
|
280
|
-
|
|
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
|
|
193
|
+
Issues and PRs welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup and [AGENTS.md](./AGENTS.md) for code conventions.
|
|
284
194
|
|
|
285
195
|
---
|
|
286
196
|
|