home/ modules/ interaction-motion-and-transforms

Interaction, motion, and coordinate transforms in p5.js

  • 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

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.

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

mouseX, mouseY and pmouseX, pmouseY give current and previous cursor positions for interaction
Concept L2 First instrument H
Processing keyboard events use keyPressed and key variables, with keyCode for special keys
Concept L2 First instrument H
Processing event functions like mousePressed() interrupt draw() cleanly, running once per event
Concept L2 First instrument H
Easing moves a value toward a target by a fraction of the remaining distance each frame
Concept L2 First instrument H
frameCount % width produces seamlessly looping horizontal motion in p5.js
Procedure L2 First instrument H
Drawing a semi-transparent black rectangle each frame creates a motion trail by gradual fade
Concept L2 First instrument H
translate() shifts Processing's coordinate origin, and pushMatrix/popMatrix save and restore transform state
Concept L2 First instrument H
rotate() and scale() in Processing transform geometry in local coordinate space, not screen space
Concept L2 First instrument H
translate() and rotate() transform the drawing origin; pushMatrix/popMatrix save and restore it
Procedure L2 First instrument H
push() and pop() isolate transformations so they do not accumulate globally in p5.js
Procedure L2 First instrument H

Supporting — enrichment, not gating

norm(), lerp(), and map() convert and interpolate values between numeric ranges
Concept L2 First instrument H
Arrays in Processing store multiple values under one name, accessed by zero-based index
Concept L2 First instrument H
In p5.js WEBGL mode the coordinate origin is at the canvas center, not the top-left
Concept L2 First instrument HG
Recording performed gestures as animation data creates more natural motion than keyframing
Concept L2 First instrument HI
Every visual animation is a parameter driven by a function of time — the character of motion is entirely in that function
Concept L2 First instrument HG
Geometric motion should be restrained and mechanical-but-eased so the eye can follow every edge
Principle L2 First instrument HG
Minimal motion is a single well-timed easing-curve drift — one move is the whole event
Concept L2 First instrument HL