P5-wrapper/react(新しいタブで開きます)
主要言語: TypeScript
A wrapper component that allows you to utilise P5 sketches within React apps.
- star 数
- 566
- watcher 数
- 8
- fork 数
- 51
- issue 数
- 0
- 最終更新
- 2026/10/30
主要言語: TypeScript
A wrapper component that allows you to utilise P5 sketches within React apps.
A component to integrate P5.js sketches into React apps.
Migrating from version 4?
Breaking changes in v5:
Component rename
ReactP5Wrapper → P5CanvasTypes
P5WrapperProps → P5CanvasPropsP5WrapperClassName → CanvasContainerClassNamePeer dependencies
p5 >= 2.0.0react >= 19.0.0react-dom >= 19.0.0If you are still using version 4, you can find the documentation here.
[npm|yarn|pnpm] [install|add] p5 @p5-wrapper/react
p5, react and react-dom are peer dependencies and must be installed in
your project.
p5 ships its own type definitions, so no extra @types package is needed —
TypeScript picks the types up from the p5 package automatically.
For Next.js applications, use our Next.js dynamic implementation:
[npm|yarn|pnpm] [install|add] p5 @p5-wrapper/next @p5-wrapper/react
See the Next.js dynamic implementation docs for further details.
A live demo can be viewed at P5-wrapper.github.io/react. The repository also contains example sketches.
To run the examples locally:
git clone git@github.com:<your username>/react.git cd react pnpm install pnpm dev
Then open http://localhost:5173 in a browser.
import * as React from "react"; import { P5Canvas } from "@p5-wrapper/react"; function sketch(p5) { p5.setup = () => p5.createCanvas(600, 400, p5.WEBGL); p5.draw = () => { p5.background(250); p5.normalMaterial(); p5.push(); p5.rotateZ(p5.frameCount * 0.01); p5.rotateX(p5.frameCount * 0.01); p5.rotateY(p5.frameCount * 0.01); p5.plane(100); p5.pop(); }; } export function App() { return <P5Canvas sketch={sketch} />; }
The sketch prop is a function that receives a p5 instance. You can type it
using either the Sketch type or the P5CanvasInstance type:
import * as React from "react"; import { P5Canvas, Sketch } from "@p5-wrapper/react"; const sketch: Sketch = p5 => { p5.setup = () => p5.createCanvas(600, 400, p5.WEBGL); p5.draw = () => { p5.background(250); p5.normalMaterial(); p5.push(); p5.rotateZ(p5.frameCount * 0.01); p5.rotateX(p5.frameCount * 0.01); p5.rotateY(p5.frameCount * 0.01); p5.plane(100); p5.pop(); }; }; export function App() { return <P5Canvas sketch={sketch} />; }
Sketchauto-types thep5argument for you. If you prefer a regularfunctiondeclaration, you can useP5CanvasInstancedirectly:import { P5CanvasInstance } from "@p5-wrapper/react"; function sketch(p5: P5CanvasInstance) { /* ... */ }
If you prefer to split your sketch logic into separate functions:
import * as React from "react"; import { P5Canvas } from "@p5-wrapper/react"; function setup(p5) { return () => { p5.createCanvas(600, 400, p5.WEBGL); }; } function draw(p5) { return () => { p5.background(250); p5.normalMaterial(); p5.push(); p5.rotateZ(p5.frameCount * 0.01); p5.rotateX(p5.frameCount * 0.01); p5.rotateY(p5.frameCount * 0.01); p5.plane(100); p5.pop(); }; } function sketch(p5) { p5.setup = setup(p5); p5.draw = draw(p5); } export function App() { return <P5Canvas sketch={sketch} />; }
You can pass any custom props to P5Canvas. These are forwarded to the
updateWithProps method if you define it in your sketch.
updateWithProps is called on initial render and whenever the props change:
import { P5Canvas } from "@p5-wrapper/react"; import React, { useEffect, useState } from "react"; function sketch(p5) { let rotation = 0; p5.setup = () => p5.createCanvas(600, 400, p5.WEBGL); p5.updateWithProps = props => { rotation = (props.rotation * Math.PI) / 180; }; p5.draw = () => { p5.background(100); p5.normalMaterial(); p5.noStroke(); p5.push(); p5.rotateY(rotation); p5.box(100); p5.pop(); }; } export function App() { const [rotation, setRotation] = useState(0); useEffect(() => { const interval = setInterval( () => setRotation(rotation => rotation + 100), 100 ); return () => { clearInterval(interval); }; }, []); return <P5Canvas sketch={sketch} rotation={rotation} />; }
Use generics so that props passed to updateWithProps are properly typed:
import { P5Canvas, Sketch, SketchProps } from "@p5-wrapper/react"; import React, { useEffect, useState } from "react"; type MySketchProps = SketchProps & { rotation: number; }; const sketch: Sketch<MySketchProps> = p5 => { let rotation = 0; p5.setup = () => p5.createCanvas(600, 400, p5.WEBGL); p5.updateWithProps = props => { rotation = (props.rotation * Math.PI) / 180; }; p5.draw = () => { p5.background(100); p5.normalMaterial(); p5.noStroke(); p5.push(); p5.rotateY(rotation); p5.box(100); p5.pop(); }; }; export function App() { const [rotation, setRotation] = useState(0); useEffect(() => { const interval = setInterval( () => setRotation(rotation => rotation + 100), 100 ); return () => { clearInterval(interval); }; }, []); return <P5Canvas sketch={sketch} rotation={rotation} />; }
You can also write
MySketchPropsas an interface:interface MySketchProps extends SketchProps { rotation: number; }And if you prefer
P5CanvasInstanceoverSketch, generics work the same way:function sketch(p5: P5CanvasInstance<MySketchProps>) { /* ... */ }
If you need to bridge React state updates from within the p5 lifecycle — for
example, reading frameCount or other instance properties to drive React state
— you can use the updater prop. It receives a callback that runs alongside
updateWithProps on every props change, but lives in the React layer:
import { P5Canvas, Sketch, SketchProps, Updater } from "@p5-wrapper/react"; import React, { useCallback, useState } from "react"; type MySketchProps = SketchProps & { rotation: number }; const sketch: Sketch<MySketchProps> = p5 => { let rotation = 0; p5.setup = () => p5.createCanvas(600, 400, p5.WEBGL); p5.updateWithProps = props => { rotation = (props.rotation * Math.PI) / 180; }; p5.draw = () => { p5.background(100); p5.normalMaterial(); p5.noStroke(); p5.push(); p5.rotateY(rotation); p5.box(100); p5.pop(); }; }; export function App() { const [rotation, setRotation] = useState(0); const [frameCount, setFrameCount] = useState(0); const updater = useCallback<Updater<MySketchProps>>((instance, props) => { setFrameCount(instance.frameCount); }, []); return <P5Canvas sketch={sketch} updater={updater} rotation={rotation} />; }
The updater callback receives the p5 instance and the current sketch props. It
is not passed to updateWithProps, so it does not leak React concerns into your
sketch logic.
To render a component on top of the sketch, add it as a child of P5Canvas and
use the exported CanvasContainerClassName constant to style the overlay via
CSS.
import { CanvasContainerClassName, P5Canvas } from "@p5-wrapper/react"; import styled, { createGlobalStyle } from "styled-components"; const GlobalWrapperStyles = createGlobalStyle` .${CanvasContainerClassName} { position: relative; } `; const StyledCentredText = styled.span` .${CanvasContainerClassName} & { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: white; font-size: 2rem; margin: 0; text-align: center; } `; export function App() { const [rotation, setRotation] = useState(0); useEffect(() => { const interval = setInterval( () => setRotation(rotation => rotation + 100), 100 ); return () => { clearInterval(interval); }; }, []); return ( <Fragment> <GlobalWrapperStyles /> <P5Canvas sketch={sketch} rotation={rotation}> <StyledCentredText>Hello world!</StyledCentredText> </P5Canvas> </Fragment> ); }
You can use any CSS-in-JS library or plain CSS — just target the wrapper class as your root selector.
If the sketch prop is undefined, you can provide a fallback UI:
import * as React from "react"; import { P5Canvas } from "@p5-wrapper/react"; function sketchOne(p5) { p5.setup = () => p5.createCanvas(600, 400, p5.WEBGL); p5.draw = () => { p5.background(250); p5.normalMaterial(); p5.push(); p5.rotateZ(p5.frameCount * 0.01); p5.rotateX(p5.frameCount * 0.01); p5.rotateY(p5.frameCount * 0.01); p5.plane(100); p5.pop(); }; } function sketchTwo(p5) { p5.setup = () => p5.createCanvas(600, 400, p5.WEBGL); p5.draw = () => { p5.background(500); p5.normalMaterial(); p5.push(); p5.rotateZ(p5.frameCount * 0.01); p5.rotateX(p5.frameCount * 0.01); p5.rotateY(p5.frameCount * 0.01); p5.plane(100); p5.pop(); }; } export function App() { const [sketch, setSketch] = React.useState(undefined); const chooseNothing = () => setSketch(undefined); const chooseSketchOne = () => setSketch(sketchOne); const chooseSketchTwo = () => setSketch(sketchTwo); return ( <> <ul> <li> <button onClick={chooseNothing}>Choose nothing</button> </li> <li> <button onClick={chooseSketchOne}>Choose sketch 1</button> </li> <li> <button onClick={chooseSketchTwo}>Choose sketch 2</button> </li> </ul> <P5Canvas fallback={<h1>No sketch selected yet.</h1>} sketch={sketch} /> </> ); }
When no sketch is selected, the fallback UI is shown. Selecting a sketch replaces it with the canvas.
You can pass error and loading props to customise what is shown when
something goes wrong or while the component is loading.
Pass a function to the error prop to handle errors thrown within the sketch or
its children:
import * as React from "react"; import { P5Canvas, P5CanvasInstance } from "@p5-wrapper/react"; function ErrorChild() { throw new Error("oops"); } function ErrorUI(error: unknown) { if (error instanceof Error) { return <p>An error occurred: {error.message}</p>; } return <p>An unknown error occurred: {String(error)}</p>; } function sketch(p5: P5CanvasInstance) { p5.setup = () => p5.createCanvas(600, 400, p5.WEBGL); p5.draw = () => { p5.background(250); p5.normalMaterial(); p5.push(); p5.rotateZ(p5.frameCount * 0.01); p5.rotateX(p5.frameCount * 0.01); p5.rotateY(p5.frameCount * 0.01); p5.plane(100); p5.pop(); }; } export function App() { return ( <P5Canvas sketch={sketch} error={ErrorUI}> <ErrorChild /> </P5Canvas> ); }
JS/TS allow you to
throwany value, not justErrorinstances. Always checkerror instanceof Errorbefore accessing.message.
Pass a function to the loading prop to show a custom UI while the component is
being lazy loaded:
import * as React from "react"; import { P5Canvas, P5CanvasInstance } from "@p5-wrapper/react"; function LoadingUI() { return <p>The sketch is being loaded.</p>; } function sketch(p5: P5CanvasInstance) { p5.setup = () => p5.createCanvas(600, 400, p5.WEBGL); p5.draw = () => { p5.background(250); p5.normalMaterial(); p5.push(); p5.rotateZ(p5.frameCount * 0.01); p5.rotateX(p5.frameCount * 0.01); p5.rotateY(p5.frameCount * 0.01); p5.plane(100); p5.pop(); }; } export function App() { return <P5Canvas sketch={sketch} loading={LoadingUI} />; }
Since P5 is used in
instance mode,
plugins are not loaded automatically. You need to set p5 on the window
object before importing the plugin:
import * as p5 from "p5"; import { P5Canvas, Sketch } from "@p5-wrapper/react"; import React, { useEffect, useState } from "react"; (window as any).p5 = p5; await import("p5/lib/addons/p5.sound"); const sketch: Sketch = p5 => { let song: p5.SoundFile; let button: p5.Element; p5.setup = () => { p5.createCanvas(600, 400, p5.WEBGL); p5.background(255, 0, 0); button = p5.createButton("Toggle audio"); button.mousePressed(() => { if (!song) { const songPath = "/piano.mp3"; song = p5.loadSound( songPath, () => { song.play(); }, () => { console.error( `Could not load the requested sound file ${songPath}` ); } ); return; } if (!song.isPlaying()) { song.play(); return; } song.pause(); }); }; p5.draw = () => { p5.background(250); p5.normalMaterial(); p5.push(); p5.rotateZ(p5.frameCount * 0.01); p5.rotateX(p5.frameCount * 0.01); p5.rotateY(p5.frameCount * 0.01); p5.plane(100); p5.pop(); }; }; export default function App() { return <P5Canvas sketch={sketch} />; }
Key points:
p5 must be set on window before importing the plugin.Using constructors (e.g. p5.Vector)Note: This example requires top level await, dynamic imports and the stream API (HTTPS only).
In instance mode, constructors are accessed via p5.constructor instead of the
global p5 namespace:
| Constructor | Global mode | Instance mode |
|---|---|---|
| Vector | p5.Vector | p5.constructor.Vector |
| Envelope | p5.Envelope | p5.constructor.Envelope |
For example, to get a random 2D vector, call p5.constructor.Vector.random2D()
instead of p5.Vector.random2D().
The source code for the component is in the src directory and the demo
application is in the demo directory. You will need
Node.js and pnpm — the exact versions
are pinned in package.json.
To build, watch and serve the examples (which also watches the component source):
pnpm dev
Before opening a pull request, make sure your changes pass every quality gate:
pnpm integrate
This runs the same checks as CI — formatting, linting, tests, and builds. Pull
requests are reviewed against the
pull request template and must keep the
public API in src/main.tsx backwards compatible. Commits follow the
conventional commit style (feat:,
fix:, chore:, etc.) — emoji prefixes are only used by automated pull
requests.
The typescript dependency is pinned to 6.0.3 on purpose:
typescript-eslint does not support TypeScript 7
yet — the Go-based v7 is not feature compatible with the v6 API it builds on,
and upstream support cannot land before TypeScript 7.1.x. It is tracked in
typescript-eslint#10940.
Please do not widen the pin in pull requests — we wait for full feature parity
before migrating to v7, no half measures.
Contributions developed with the assistance of an AI agent are welcome, but you — the developer — are always the ultimately responsible individual for your contributions. AI agents cannot author or certify them. When AI assistance is used:
--signoff (git commit -s) so that you certify the
Developer Certificate of Origin for the
work yourselfAssisted-by: LLM trailer in the commit
message, as
recommended by the Linux kernel teamSee AGENTS.md for the full set of rules agents follow in this
repository.