Fluid simulation in React with WebGL, pure TypeScript, and zero runtime dependencies.
npm install react-webgl-fluid-play
# or
yarn add react-webgl-fluid-play
# or
pnpm add react-webgl-fluid-playRequires React 17+ and ReactDOM 17+ as peer dependencies.
import Canvas from 'react-webgl-fluid-play';
export default function App() {
return (
<div style={{ position: 'fixed', inset: 0 }}>
<Canvas initialAnimation={{ path: 'oval' }} />
</div>
);
}The component fills 100 % of its parent, so wrap it in a positioned container that defines the size you want.
import Canvas, {
PathFollower,
PREDEFINED_PATHS,
PREDEFINED_PATH_IDS,
cloneFluidPath,
createPredefinedPath,
getPredefinedPath,
getPredefinedPaths,
isPredefinedPathId,
type CanvasAnimationOptions,
type CanvasInitialAnimation,
type CanvasProps,
type CanvasRef,
type FluidPath,
type FluidPathOverrides,
type PathColor,
type PathPoint,
type PredefinedPathId,
} from 'react-webgl-fluid-play';Canvasdefault export (and named export)PathFollowerclass export- predefined path exports:
PREDEFINED_PATHS,PREDEFINED_PATH_IDS,createPredefinedPath,getPredefinedPath,getPredefinedPaths,isPredefinedPathId,cloneFluidPath - path/type exports:
FluidPath,FluidPathOverrides,PathPoint,PathColor,PredefinedPathId - canvas API types:
CanvasRef,CanvasProps,CanvasAnimationOptions,CanvasInitialAnimation
oval, infinity, spiral, wave, star, heart
| Prop | Type | Default | Description |
|---|---|---|---|
width |
number |
window width | Fixed pixel width of the canvas. Omit to auto-track the viewport. |
height |
number |
window height | Fixed pixel height of the canvas. Omit to auto-track the viewport. |
className |
string |
— | CSS class applied to the outer wrapper <div>. |
style |
React.CSSProperties |
— | Inline styles merged into the outer wrapper <div>. |
onLoad |
() => void |
— | Called once the WebGL fluid simulation has successfully initialised. |
onError |
(error: string) => void |
— | Called if WebGL initialisation fails, with a human-readable error message. |
showPathManager |
boolean |
false |
Show the built-in path-selector button and panel. Omit or set false for a completely bare canvas. |
initialAnimation |
{ path: FluidPath | PredefinedPathId; options?: CanvasAnimationOptions } |
— | Starts a path animation automatically once the simulation is ready. |
loadingFallback |
React.ReactNode |
spinning ring | Rendered while the simulation is initialising. Pass null to show nothing. |
errorFallback |
(error: string, retry: () => void) => React.ReactNode |
minimal error + retry button | Rendered when initialisation fails. Receives the error message and a retry callback. |
CanvasAnimationOptions
maxLoops?: number(-1or omitted = infinite)predefinedOverrides?: FluidPathOverrides(used when path is a predefined id)
CanvasRef
playPath(path, options?)playPredefinedPath(pathId, options?)stopPath()clearPath()(alias ofstopPath())isPlayingPath()getCurrentPath()
<Canvas /><Canvas showPathManager /><Canvas initialAnimation={{ path: 'heart', options: { maxLoops: 1 } }} /><Canvas width={800} height={600} /><Canvas
onLoad={() => console.log('simulation ready')}
onError={(msg) => console.error('WebGL failed:', msg)}
/><Canvas loadingFallback={<MySpinner />} />
// hide loading UI entirely
<Canvas loadingFallback={null} /><Canvas
errorFallback={(error, retry) => (
<div>
<p>{error}</p>
<button onClick={retry}>Try again</button>
</div>
)}
/><Canvas
className="my-fluid-canvas"
style={{ borderRadius: '12px', overflow: 'hidden' }}
/>import { useRef } from 'react';
import Canvas, { type CanvasRef, createPredefinedPath } from 'react-webgl-fluid-play';
export default function App() {
const canvasRef = useRef<CanvasRef>(null);
return (
<>
<button onClick={() => canvasRef.current?.playPredefinedPath('infinity')}>Infinity</button>
<button onClick={() => canvasRef.current?.playPredefinedPath('heart', { maxLoops: 1 })}>
Heart Once
</button>
<button
onClick={() => {
const custom = createPredefinedPath('wave', { duration: 3, loop: false });
canvasRef.current?.playPath(custom);
}}
>
Custom Wave Variant
</button>
<button onClick={() => canvasRef.current?.stopPath()}>Stop</button>
<Canvas ref={canvasRef} />
</>
);
}- Mobile: the component automatically uses
window.visualViewportfor accurate dimensions on mobile browsers and prevents default touch scroll/zoom behaviour on the canvas. - Dimensions: when
width/heightprops are omitted the canvas trackswindow.innerWidth/window.innerHeight(andvisualViewporton mobile), reacting to resize and orientation-change events. - Path coordinates: custom paths use normalized coordinates (
x/yin[0,1]). - WebGL: requires a browser with WebGL support and hardware acceleration enabled. The
onErrorcallback (or the default error UI) is shown when the context cannot be created. - Layout: the component renders as
position: relative; width: 100%; height: 100%. Place it inside a container that defines the actual size (e.g.position: fixed; inset: 0for full-screen).
pnpm start- run the demo app in development mode.pnpm build- build the demo app (react-scripts build).pnpm build:lib- build library artifacts todist/(CJS, ESM, and.d.ts).pnpm lint- lintsrc/andscripts/with ESLint.pnpm lint:fix- lint with auto-fixes.pnpm prepublishOnly- build library artifacts before publishing.
- Module formats: ships both CommonJS (
main) and ESM (module) builds. - Type declarations: includes bundled TypeScript definitions (
types). - Tree-shaking: no side effects, so bundlers can safely drop unused imports.
MIT © Maifee Ul Asad