Interaction, motion, and coordinate transforms in p5.js
Learning objectives
- learner can capture mouse, keyboard, and event input to make a sketch interactive
- learner can animate with easing, frame-count timing, and motion-blur trails
- learner can compose transforms with translate/rotate/scale and isolate them with push/pop matrix stacks
Capstone — one whole task that evidences the objectives
Produce an interactive kinetic sketch where mouse and keyboard input steer eased, looping motion of transformed shapes, using push/pop so local transforms and motion trails compose cleanly.
Prerequisite modules
This module is where a static sketch becomes an instrument. In a live-visuals or VJ context — p5.js projected behind a set, reacting to the performer at the keyboard — the difference between a demo and a performance is exactly this trio: input you can play, motion that feels alive rather than teleported, and transforms that stay local so one gesture never corrupts the rest of the scene.
Start supported: wire the cursor to a single shape using the current-versus-previous mouse position idiom, then add one key command via the event functions that fire once per press instead of every frame. Next, replace direct position-setting with easing — move a fraction of the remaining distance each frame — so the shape chases the cursor organically, and let frame-count-modulo drive a seamlessly looping sweep. Swap the full-canvas clear for the semi-transparent-rectangle fade to earn motion trails. Finally, rebuild the shape as a locally transformed object: translate to its center, rotate and scale there, and wrap the whole thing in push/pop so trails and multiple shapes compose without transform drift.
Every required atom gates the capstone directly: without event handling the sketch is not steerable, without easing and the fade technique the motion reads as mechanical, and without matrix isolation the composed scene falls apart the moment a second shape appears. The supporting atoms widen the craft — mapping input ranges onto rotation angles, arrays for scaling one shape into a swarm, the WEBGL center-origin gotcha when you later go 3D, and Bret Victor’s gesture-recording idea as a lens on why performed motion beats keyframed motion. Drill easing updates, push/pop pairing, and modulo looping until they are reflexes; on stage there is no time to derive them.
Walkthrough
You’ll turn a static sketch into an instrument — mouse-steered, eased, looping motion of transformed shapes that compose without stepping on each other. Paste each sketch into the p5 web editor and press Run (▶); move the mouse and press keys to play it. Each step is a complete, standalone sketch.
1 — the mouse is a live input. mouseX/mouseY hold the cursor now; pmouseX/pmouseY hold it last frame. Draw a shape at the cursor and a line between the two, and the line length is your speed — the sketch already reads your hand ([[processing-mouse-input]]).
function setup() { createCanvas(400, 400); }
function draw() {
background(20);
noStroke();
fill(255, 140, 0);
circle(mouseX, mouseY, 40); // follows the cursor
stroke(255, 120);
strokeWeight(2);
line(pmouseX, pmouseY, mouseX, mouseY); // this-frame vs last-frame = speed
}
2 — keys that fire once. keyPressed() is an event function: p5 calls it once on the down-stroke, not every frame — so it’s where discrete commands live (change a colour, drop a shape). Here each press advances the hue by a step ([[processing-keyboard-input]], [[processing-event-functions]]).
let hue = 200;
function setup() { createCanvas(400, 400); colorMode(HSB, 360, 100, 100); noStroke(); }
function keyPressed() { hue = (hue + 40) % 360; } // once per press, not per frame
function draw() {
background(0, 0, 12);
fill(hue, 80, 90);
circle(200, 200, 160);
}
3 — easing makes motion organic. Don’t set position directly — move a fraction of the remaining distance to the target each frame (pos += (target − pos) * 0.08). The shape chases the cursor and decelerates as it arrives, instead of teleporting ([[processing-easing-motion]]).
let x = 200, y = 200;
function setup() { createCanvas(400, 400); noStroke(); }
function draw() {
background(20);
x += (mouseX - x) * 0.08; // close 8% of the gap each frame
y += (mouseY - y) * 0.08;
fill(120, 200, 255);
circle(x, y, 44);
}
4 — a seamless loop from frameCount. frameCount % N gives a sawtooth 0→N that repeats forever; divide it to a 0→1 phase and feed sin(phase * TWO_PI) for motion that loops with no seam — the backbone of autonomous (hands-off) movement ([[p5js-framecount-modulo-animation]]).
function setup() { createCanvas(400, 400); noStroke(); }
function draw() {
background(20);
let t = (frameCount % 120) / 120; // 0→1 every 120 frames, seamless
let x = 200 + 140 * sin(t * TWO_PI);
fill(255, 90, 120);
circle(x, 200, 40);
}
5 — motion trails. Replace the opaque background() clear with a translucent full-canvas rectangle: old frames fade instead of vanishing, leaving a trail. Alpha controls trail length — lower alpha, longer tail ([[processing-motion-blur-technique]]).
function setup() { createCanvas(400, 400); noStroke(); }
function draw() {
fill(20, 30); // translucent wash instead of a hard clear
rect(0, 0, width, height);
let t = (frameCount % 120) / 120;
let x = 200 + 140 * sin(t * TWO_PI);
let y = 200 + 90 * sin(t * TWO_PI * 2); // a second rate → a figure-eight
fill(120, 255, 180);
circle(x, y, 30);
}
6 — push/pop transforms (the capstone). Build each shape at the local origin, then translate/rotate/scale the coordinate system to place it — and wrap every shape in push()/pop() so its transform is isolated and the next shape starts clean. Mouse steers the spin, frameCount drives the orbit, trails tie it together: an instrument you play ([[p5js-push-pop-transformations]], [[processing-translation-matrix]], [[processing-rotate-scale]]):
function setup() { createCanvas(400, 400); rectMode(CENTER); }
function draw() {
noStroke();
fill(16, 40);
rect(200, 200, width, height); // trail wash
let steer = map(mouseX, 0, width, -1, 1);// mouse steers spin
let t = (frameCount % 240) / 240;
for (let i = 0; i < 3; i++) {
push(); // isolate this shape's coordinate system
translate(200 + 90 * sin(t * TWO_PI + i * 2),
200 + 90 * cos(t * TWO_PI + i * 2));
rotate(frameCount * 0.02 * (steer + 0.4) + i);
scale(0.6 + 0.4 * sin(t * TWO_PI + i));
fill(200 - i * 50, 150, 255);
rect(0, 0, 70, 70); // drawn at local origin, transform-isolated
pop(); // restore — next shape unaffected
}
}
What good looks like. Performed motion should feel alive: it eases (accelerates and settles) rather than snapping, and layers at least two rates so there’s depth — the fast spin against the slow orbit here. The beginner tell is linear, single-rate motion that reads as mechanical, plus forgotten pop()s that let one shape’s rotation smear the whole scene. Check your push/pop calls always balance, and that the trail length serves the piece (long tails for flow, short for punch). (Skill map: live-visualist Domain B3 — motion, rhythm, and timing; ease, layer rates, hold stillness.)
Now make it yours. map() the mouse’s Y onto trail alpha for a hands-on blur control. Swap the three shapes for an array of ten. Add a keyPressed() that toggles rectMode/shape. Drive scale from the mouse and rotate from frameCount. Nest a second push/pop inside the loop to orbit a child shape around each parent.
Runnable examples
Generated from the context/ instrument corpus by concept (redistributable idioms only). Do not edit — regenerate with gen-module-examples.mjs.
rotation-animation
uv = rot(u_time * 0.3) * uv;
glsl-0029 · public-domain
osc(10).rotate(() => time * 0.2).out()
hydra-0006 · CC0-1.0
oscillation
let y = height/2 + sin(frameCount * 0.05) * 100
p5live-0004 · CC0-1.0
float rings = abs(sin(length(uv)*20.0 - u_time*2.0));
glsl-0039 · public-domain
easing-curve
x = lerp(x, targetX, 0.1)
p5live-0032 · CC0-1.0
noise-drift
let x = noise(frameCount*0.002)*width, y = noise(frameCount*0.003)*height
p5live-0007 · CC0-1.0
Atoms in this module
Required — these gate the capstone
Supporting — enrichment, not gating
Part of curricula
- Live Visualist — zero to performing live-coded & generative visuals — Generative canvas — colour, motion, and Hydra live-coding required
- VJ — visual performance with projection, light & video — Generate & compose: build your own look recommended
Unlocks — modules that require this one