Learn Creative Coding (#6) - Interactivity: Mouse, Keyboard, and Touch

in #stem3 days ago (edited)

Learn Creative Coding (#6) - Interactivity: Mouse, Keyboard, and Touch

banner

What will I learn

  • Tracking mouse position with mouseX and mouseY;
  • responding to clicks and key presses;
  • building a simple drawing tool;
  • making sketches respond to user input in interesting ways;
  • touch support for mobile devices.

Requirements

  • A modern web browser;
  • Episodes #1-5 of this series.

Difficulty

  • Beginner

Curriculum (of the Learn Creative Coding Series):


Learn Creative Coding (#6) - Interactivity: Mouse, Keyboard, and Touch

Up to now our sketches have been self-contained. The code runs, the art appears, the viewer watches. That's fine for generative prints and NFTs. But some of the most exciting creative coding is interactive - the viewer becomes a participant.

Mouse position

p5.js gives you mouseX and mouseY for free. They update every frame.

function setup() {
  createCanvas(500, 500);
}

function draw() {
  background(20);
  noStroke();
  fill(255, 100, 150);
  ellipse(mouseX, mouseY, 40, 40);
}

A circle that follows your mouse. Nothing special yet. Let's make it interesting:

function setup() {
  createCanvas(500, 500);
  background(20);
  noCursor();
}

function draw() {
  // don't clear background - paint mode
  noStroke();
  
  let size = dist(mouseX, mouseY, pmouseX, pmouseY);
  size = constrain(size, 2, 50);
  
  fill(mouseX % 255, 150, mouseY % 255, 30);
  ellipse(mouseX, mouseY, size, size);
}

Now we're painting. pmouseX and pmouseY are the previous frame's mouse position. The distance between current and previous tells us how fast the mouse is moving - fast movement = big brush, slow movement = tiny dots. The color shifts based on position. Move slowly and you get fine detail. Slash across the canvas and you get bold streaks.

constrain(value, min, max) keeps the size within bounds. Without it, a fast flick would draw a dot the size of the canvas.

Mouse events

Beyond tracking position, you can respond to specific events:

let shapes = [];

function setup() {
  createCanvas(500, 500);
}

function draw() {
  background(240);
  noStroke();
  
  for (let s of shapes) {
    fill(s.c);
    ellipse(s.x, s.y, s.size, s.size);
  }
}

function mousePressed() {
  shapes.push({
    x: mouseX,
    y: mouseY,
    size: random(20, 80),
    c: color(random(255), random(100, 200), random(150, 255), 180)
  });
}

Every click adds a circle. The array grows, the canvas fills up. mousePressed() fires once per click - not continuously. For continuous drawing while the mouse is held, check mouseIsPressed inside draw:

function draw() {
  if (mouseIsPressed) {
    fill(255, 50);
    noStroke();
    ellipse(mouseX, mouseY, 20, 20);
  }
}

Mouse as influence, not position

Here's where it gets creative. Instead of drawing at the mouse, use the mouse to influence the entire scene:

function setup() {
  createCanvas(500, 500);
}

function draw() {
  background(20);
  noStroke();
  
  let spacing = 25;
  
  for (let x = spacing; x < width; x += spacing) {
    for (let y = spacing; y < height; y += spacing) {
      let d = dist(x, y, mouseX, mouseY);
      let size = map(d, 0, 200, 20, 3);
      size = constrain(size, 3, 20);
      
      let brightness = map(d, 0, 200, 255, 50);
      fill(brightness, 100, 200, 180);
      ellipse(x, y, size, size);
    }
  }
}

A grid of dots that swells near the mouse, like a magnifying glass effect. The mouse creates a "field of influence" - nearby dots grow, distant dots shrink. Move the mouse around and the whole grid responds. This is much more interesting than just "draw a circle where the mouse is."

Keyboard input

keyPressed() fires when any key is pressed. Use key for the character and keyCode for special keys.

let bg = 20;
let palette = ['#ff6b6b', '#feca57', '#48dbfb', '#ff9ff3', '#54a0ff'];
let currentColor = 0;

function setup() {
  createCanvas(500, 500);
  background(bg);
}

function draw() {
  if (mouseIsPressed) {
    noStroke();
    fill(palette[currentColor]);
    let size = 5 + sin(frameCount * 0.1) * 3;
    ellipse(mouseX, mouseY, size, size);
  }
}

function keyPressed() {
  if (key === 'c') {
    background(bg);  // clear
  }
  if (key === ' ') {
    currentColor = (currentColor + 1) % palette.length;  // cycle color
  }
  if (key === 's') {
    saveCanvas('my-drawing', 'png');  // save!
  }
}

A mini drawing app. Click and drag to paint, spacebar to change color, 'c' to clear, 's' to save. That saveCanvas() function is built into p5.js - super handy for grabbing screenshots of your work.

Combining mouse with animation

Mouse input and procedural animation work beautifully together:

let particles = [];

function setup() {
  createCanvas(600, 600);
}

function draw() {
  background(15, 30);
  
  // spawn particles at mouse when pressed
  if (mouseIsPressed) {
    for (let i = 0; i < 3; i++) {
      particles.push({
        x: mouseX,
        y: mouseY,
        vx: random(-2, 2),
        vy: random(-3, -0.5),
        life: 255,
        size: random(3, 8),
        col: [random(150, 255), random(80, 180), random(200, 255)]
      });
    }
  }
  
  // update and draw particles
  for (let i = particles.length - 1; i >= 0; i--) {
    let p = particles[i];
    p.x += p.vx;
    p.y += p.vy;
    p.vy += 0.05;  // gravity
    p.life -= 3;
    
    if (p.life <= 0) {
      particles.splice(i, 1);
      continue;
    }
    
    noStroke();
    fill(p.col[0], p.col[1], p.col[2], p.life);
    ellipse(p.x, p.y, p.size, p.size);
  }
}

Click and drag to spray particles. They float up, gravity pulls them down, they fade and die. The backwards loop (i--) is important when removing elements from an array while iterating - going forward would skip elements after a splice.

We'll build a proper particle system in a later episode, but this gives you the idea.

Touch support

p5.js handles touch automatically on mobile. mouseX/mouseY map to the first touch point. For multi-touch:

function touchMoved() {
  for (let t of touches) {
    fill(random(255), 150, 200, 50);
    noStroke();
    ellipse(t.x, t.y, 30, 30);
  }
  return false;  // prevent scrolling
}

The touches array contains all active touch points. return false prevents the default browser behavior (scrolling, zooming) so your sketch captures the touch.

If you're making work for exhibition or web, testing on mobile is worth it. Fingers are less precise than a mouse but they're more physical - people engage differently with touch.

A simple interactive piece

Let me put everything together into something that works as a standalone interactive artwork:

let trails = [];

function setup() {
  createCanvas(600, 600);
  background(15);
  noCursor();
}

function draw() {
  // slow fade instead of clear
  background(15, 8);
  
  // connection lines between recent trail points
  stroke(255, 40);
  strokeWeight(0.5);
  for (let i = 1; i < trails.length; i++) {
    let a = trails[i - 1];
    let b = trails[i];
    if (dist(a.x, a.y, b.x, b.y) < 100) {
      line(a.x, a.y, b.x, b.y);
    }
  }
  
  // draw trail points
  noStroke();
  for (let i = trails.length - 1; i >= 0; i--) {
    let t = trails[i];
    t.life -= 1;
    t.x += sin(t.angle) * 0.3;
    t.y += cos(t.angle) * 0.3;
    
    if (t.life <= 0) {
      trails.splice(i, 1);
      continue;
    }
    
    fill(t.hue, 150, 220, t.life);
    ellipse(t.x, t.y, map(t.life, 0, 200, 1, 6), map(t.life, 0, 200, 1, 6));
  }
  
  // add new points at mouse
  if (frameCount % 2 === 0) {
    trails.push({
      x: mouseX + random(-5, 5),
      y: mouseY + random(-5, 5),
      life: 200,
      hue: (frameCount * 0.5) % 255,
      angle: random(TWO_PI)
    });
  }
  
  // cursor glow
  fill(255, 30);
  ellipse(mouseX, mouseY, 20, 20);
}

function keyPressed() {
  if (key === 's') saveCanvas('interactive-trails', 'png');
  if (key === 'c') {
    trails = [];
    background(15);
  }
}

Move your mouse around. Glowing dots trail behind and slowly drift apart, connected by faint lines. The color cycles over time. Press 's' to save, 'c' to clear. It's meditative - the kind of thing you can play with for way longer than you'd expect.

't Komt erop neer...

  • mouseX/mouseY track position, pmouseX/pmouseY give previous frame
  • mousePressed() for clicks, mouseIsPressed for continuous drawing
  • keyPressed() + key for keyboard input, saveCanvas() to export
  • Use the mouse as influence, not just position - distance fields, attraction, repulsion
  • dist(), map(), constrain() are your three best friends for interactive work
  • touches array for multi-touch on mobile
  • Remove particles backwards (i--) when splicing during iteration

Next episode: color theory for coders. We'll leave RGB behind and learn to think in HSB - hue, saturation, brightness. Makes sense, right? Way more intuitive for choosing colors that actually look good together.

Merci en tot de volgende!

X

@femdev