Running Multiple p5.js Sketches on One Page Without Collisions: Instance Mode
p5.js global mode breaks down the moment you embed sketches in a larger page or run two at once. Instance mode fixes it with a small, mechanical rewrite — here's how, and what it costs.
10 Jul 2026, 06:14 UTC

Drop two p5.js sketches into the same page in the default style and one of them quietly wins. Both define a global setup() and draw(), both expect createCanvas() to exist on window, and the second script to load overwrites the first. If you are embedding a sketch inside a larger page — a React component, an interactive article, a gallery of demos — this is the wall you hit first. The fix is instance mode, and it is a smaller change than it looks.
What global mode actually does
p5.js was designed to be friendly to beginners, so its default behavior is aggressive: when the library loads, it scans for functions like setup, draw, and mousePressed on the global scope and wires them up. It also injects its entire API — createCanvas, ellipse, width, PI, hundreds of names — onto window.
That is fine for a standalone sketch page. It is a problem when:
- Your application code (or another library) already uses a name p5 wants, or vice versa.
- You want two or more sketches on one page — they share one global namespace, so they collide.
- You need to destroy a sketch cleanly when a user navigates away, which matters in single-page apps where components mount and unmount.
Instance mode: one object, one sketch
Instance mode wraps your sketch in a function that receives a p5 object — conventionally named p — and every API call becomes a method or property on that object. You then construct the sketch explicitly:
const sketch = (p) => {
let x = 0;
p.setup = () => {
p.createCanvas(300, 200);
};
p.draw = () => {
p.background(20);
p.ellipse(x, 100, 40, 40);
x = (x + 2) % p.width;
};
};
const instance = new p5(sketch, 'sketch-container');Run this in a plain script tag after loading p5.js (or in a module bundler with p5 imported). The second argument to new p5() is optional but useful: pass a DOM element or an element id, and the canvas mounts inside that container instead of being appended to document.body. That alone solves most layout headaches when embedding a sketch in a page with real CSS around it.
Every sketch gets its own p object, its own canvas, and its own draw loop. Nothing is shared, so two instances on one page are fully independent.
Worked example: two bouncing balls, side by side
Here is the same animation instantiated twice with different parameters, mounted into two containers:
const bounce = (speed, hue) => (p) => {
let x = 0;
p.setup = () => {
p.createCanvas(200, 150);
p.colorMode(p.HSB);
};
p.draw = () => {
p.background(0, 0, 15);
p.fill(hue, 80, 90);
p.noStroke();
p.circle(x, p.height / 2, 30);
x = (x + speed) % p.width;
};
};
const left = new p5(bounce(2, 200), 'left-sketch');
const right = new p5(bounce(5, 330), 'right-sketch');With two elements <div id="left-sketch"></div> and <div id="right-sketch"></div> in the page, you get two isolated animations. Because the sketch is just a factory function, parameterizing it (speed, color) is ordinary JavaScript — no globals, no duplication.
To verify isolation, open the page and confirm both canvases animate at different speeds. Then, in the browser console, run left.remove(): the left canvas should disappear and its loop should stop, while the right one keeps running. That p.remove() call is the teardown hook that makes instance mode viable in frameworks — call it from a React useEffect cleanup or a Vue unmounted hook and the sketch shuts down without leaking animation frames.
The translation tax
The honest cost of instance mode is that most p5.js examples, tutorials, and forum answers are written in global mode. When you copy a snippet, you must translate it: prefix every p5 function and constant with p. — including easy-to-miss ones like p.PI, p.width, p.mouseX, and constants such as p.HSB. Miss one and the sketch fails silently or throws a ReferenceError for a name that "obviously exists" in the examples you were reading.
Two practical mitigations:
- Keep a mental checklist of the non-obvious globals: constants (
PI,TWO_PI,HSB), state variables (width,height,frameCount,mouseX), and event handlers (mousePressed,keyPressed) all need the prefix or thep.assignment form. - Let your linter help. In instance mode, an un-prefixed
ellipse(...)is an undefined variable, which ESLint or TypeScript will flag immediately — a small but real advantage over global mode, where typos can collide with real globals.
One more caveat: module-loading details (script tag versus ES module import) have shifted across p5.js releases, so check the reference for the version you are actually using before assuming a particular import style works.
When to bother
If you are writing a single standalone sketch page, global mode is fine and slightly less verbose — that is what it is for. Switch to instance mode the moment any of these become true: the sketch lives inside a larger application, more than one sketch shares a page, or you need to mount and unmount canvases without leaking state. The conversion is mechanical — wrap, prefix, construct — and the payoff is that your sketch becomes an ordinary, testable, disposable piece of JavaScript instead of a set of global side effects.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.