Small sketches built with React, Vite and p5.js.
main is the starting point. Each sketch lives on its own branch, so this one stays
generic.
npm install
npm run devnpm run build typechecks before it bundles, so run that before pushing.
src/
App.tsx wires a sketch into the page
main.tsx React entry
styles.css tailwind import and the theme colours
p5/
createSketch.ts canvas setup, sizing, teardown
useP5Sketch.ts keeps a sketch in step with React
demo/
flowField.ts the example sketch, replace this
components/ whatever the sketch needs, starts with ErrorBoundary
lib/ hooks that are not p5 specific
p5 runs in instance mode, so there is no global p5. Your sketch is a plain object
with a size and a setup function. Setup draws nothing itself; it returns the function
p5 calls every frame.
The sketch never hardcodes a canvas size. It asks for the size of the element it is mounted in, so the canvas fills whatever box you put it in and follows the element when that box changes.
// src/demo/acme.ts
import type p5 from 'p5';
import type { SketchDefinition } from '../p5/createSketch';
export function acme(readSize: () => { width: number; height: number }): SketchDefinition {
return {
readSize,
setup: (p: p5) => {
const { width, height } = readSize();
p.createCanvas(width, height);
return () => {
p.background(12, 12, 14);
p.circle(p.width / 2, p.height / 2, 80);
};
},
};
}useP5Sketch mounts it. useElementSize measures the element, and the dependency
array asks for another frame whenever that measurement changes:
const stage = useElementSize<HTMLDivElement>();
const host = useRef<HTMLDivElement | null>(null);
useP5Sketch(host, acme(() => stage.size), [stage.size.width, stage.size.height]);
return (
<div ref={stage.ref} className="h-dvh w-screen">
<div ref={host} className="h-full w-full" />
</div>
);Four things worth knowing:
readSize()is checked every frame, andresizeCanvasruns whenever the answer changes. It is what keeps the canvas the same size as its container.- The dependency array asks for another frame. That is how a control reaches the sketch.
- A
resetKeytears the sketch down and builds it again, for when the thing being drawn is replaced rather than adjusted. - Sketches that hold still can call
p.noLoop()in setup andp.redraw()when something changes, instead of painting sixty times a second. There is a note about it increateSketch.ts.
git switch -c my-sketchReplace the sketch, point App.tsx at it, and delete demo/ once you have. Add
components under src/components as it grows.
Each branch is built and published to its own directory:
| Branch | URL |
|---|---|
main |
https://madebyoutside.github.io/creative-react/ |
That is what the workflow does: it builds the branch it is running on and pushes the
result into a gh-pages branch, into a folder named after the branch. Sibling folders
are left alone, so the projects do not overwrite each other.
To add a sketch, put its branch in the branches list in
.github/workflows/pages.yml. It is a list rather than a wildcard because a wildcard
would also fire on gh-pages and delete the site.
vite.config.ts sets base: './' so a build works at the repo root and in a
subdirectory. Leave it relative.