Using p5.Graphics for Off‑Screen Drawing in p5.js
Learn how to create and use a p5.Graphics off‑screen buffer in p5.js for layered drawing, reduced redraw cost, and isolated pixel manipulation.
31 Mar 2026, 07:02 UTC

Why use an off‑screen buffer
The main canvas in a p5.js sketch is redrawn every frame by default. If you need to render a complex static layer, apply a post‑process effect, or composite several drawings without re‑executing the same drawing code each frame, an off‑screen buffer (a p5.Graphics object) lets you draw once and reuse the result.
Creating and drawing to a p5.Graphics buffer
First, allocate a buffer with the same dimensions as the sketch (or a subset if you only need a region). The buffer can use the default 2D renderer or WebGL if you enable it.
let buffer;
function setup() {
createCanvas(800, 600);
// Create an off‑screen graphics buffer; omit WEBGL for 2D mode
buffer = createGraphics(width, height);
}
function draw() {
// Clear the main canvas each frame
background(30);
// Draw into the buffer only when needed; here we draw every frame for demo
buffer.background(50, 50, 200); // buffer’s own background
buffer.noStroke();
buffer.fill(255, 100, 0);
buffer.ellipse(width / 2, height / 2, 200, 200);
// Copy the buffer’s contents onto the main canvas
image(buffer, 0, 0);
}
The createGraphics call returns a p5.Graphics object that has its own drawing state. Calling buffer.background() clears that off‑screen surface; without it, previous frames would linger and cause trailing artifacts.
Copying the buffer to the main canvas
The image() function renders the buffer’s pixel data onto the main canvas at the specified coordinates. Because the buffer is treated like an image, you can also scale, rotate, or tint it using the same parameters you would pass to image() for any p5.Image.
Limits and common mistakes
- Memory usage: Each buffer allocates memory proportional to
width × height × 4bytes (RGBA). Large buffers at highpixelDensity()can quickly exceed GPU memory, especially on mobile devices. Keep the buffer only as large as the effect requires. - Redraw cost: Drawing commands inside the buffer still execute every frame unless you move them out of
draw()or callbuffer.noLoop()and manually trigger redraws when the buffer’s content changes. - WebGL compatibility: If the main canvas uses
WEBGL, the off‑screen buffer must also be created withcreateGraphics(width, height, WEBGL). Mixing a 2D buffer with a WebGL canvas can cause context loss. - Pixel access: The
buffer.pixelsarray is not populated until you callbuffer.loadPixels(). Reading or writing pixels without this step yields undefined values. - Trailing artifacts: Forgetting
buffer.background()(or another clearing operation) leaves previous drawings visible, producing a smeared effect. - State mismatch: Drawing to the buffer after the main canvas has been cleared is fine, but drawing to the main canvas first and then expecting the buffer to retain that state will not work; the buffer is independent.
Practical verification
To confirm the buffer behaves as expected, you can:
- Open the browser’s developer console and log
buffer.widthandbuffer.heightafter creation; they should match the dimensions you passed tocreateGraphics. - Visually compare a frame drawn directly to the main canvas with the same drawing sequence performed inside the buffer and then copied with
image(). The results should appear identical when the buffer’s background matches the main canvas background. - Change the buffer’s background color (e.g.,
buffer.background(100, 0, 0)) and observe that only the buffered layer changes, proving that drawing is isolated to the off‑screen surface.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.