Why p5.js Instance Mode Is the Right Default for Real Pages
Global mode is fine for demos, but on real pages it pollutes the namespace and leaks animation loops. Here's why p5.js instance mode — with a worked example and cleanup pattern — is the practical default.
11 Apr 2026, 17:44 UTC

You've got a generative sketch working beautifully in the p5.js web editor. You paste it into your actual site — a documentation page, a React component, a blog post with two other demos — and suddenly things break: variables collide, two sketches fight over the same canvas, or an animation loop keeps running after the user navigates away. The fix isn't a workaround. It's instance mode, and it should be your default whenever a sketch lives inside a larger page.
Global mode's hidden cost
By default, p5.js runs in global mode: you define setup() and draw() as top-level functions, and p5 attaches them — along with hundreds of identifiers like mouseX, width, fill, and ellipse — to the global window scope. That's fine for a standalone demo. On a real page, it means your sketch shares a namespace with every analytics script, UI library, and other sketch you load. A variable named width in your application code and p5's width are now the same thing, and the bugs that follow are confusing because neither side looks wrong in isolation.
What instance mode changes
Instance mode wraps the sketch in a function that receives a p5 object, conventionally named p. Every API call goes through it, and nothing leaks into the global scope:
const sketch = (p) => {
let x = 50;
p.setup = () => {
p.createCanvas(300, 200);
};
p.draw = () => {
p.background(20);
p.ellipse(x, 100, 40, 40);
x = (x + 2) % p.width;
};
};
const holder = document.getElementById('sketch-holder');
const instance = new p5(sketch, holder);Run this in a page that loads p5.js (CDN or bundled — verify against the version you actually ship, since p5.js 2.x is under active development and API details may shift). The second argument to new p5() scopes the canvas to a specific DOM node, so the sketch renders exactly where your layout wants it instead of appending to <body>.
Two quick checks confirm it's working: both window.setup and window.draw should be undefined in the browser console, and the canvas element should appear inside #sketch-holder, not at the end of the body.
Multiple sketches, one page
The payoff is immediate: each new p5(fn, element) creates an isolated canvas and state. Two divs, two sketch instances, two independent animations — no shared mouseX, no collisions. This is the standard pattern for embedding generative demos in documentation, dashboards, or blog posts with several examples.
One caveat: every instance runs its own requestAnimationFrame loop. Three animated sketches means three loops, and performance degrades roughly linearly on low-end devices. For static or on-demand sketches, call p.noLoop() in setup and redraw explicitly with p.redraw() when input changes.
Lifecycle: the part frameworks make mandatory
In React, Vue, or any component system, instance mode isn't just cleaner — it's how you avoid leaks. If a component unmounts without tearing down the sketch, the draw loop and window event listeners keep running. You'll see duplicate rendering after remounts and steady memory growth.
The cleanup is one method call, remove(), which stops the loop and detaches listeners. In a React effect, for example:
useEffect(() => {
const instance = new p5(sketch, ref.current);
return () => instance.remove(); // runs on unmount
}, []);To verify teardown actually works, temporarily add a console.log inside p.draw, unmount the component, and confirm logging stops. If it doesn't, the sketch is still alive somewhere.
The honest trade-off
Instance mode is verbose. Every function and constant needs the prefix — p.ellipse, p.width, p.TWO_PI — and code copied from the p5 web editor (which assumes global mode) won't run inside the wrapper until you mechanically add p. everywhere. Miss one, and you get an undefined-function error that beginners often misdiagnose. Some teams soften this by destructuring constants or keeping a lint rule, but there's no way around the prefixing itself.
Accept that cost anyway. The moment a sketch shares a page with anything else — other scripts, other sketches, a framework — global mode's convenience turns into debugging time. Write new sketches in instance mode from the start, port old ones with a careful find-and-replace plus a manual pass, and always pair new p5() with a matching remove(). Your future embedded demos will thank you.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.