Web UI Controls Workshop with p5.js

This workshop builds on the emoji face example to explore different ways to control graphics using web UI elements.

1. Controlling Face with Buttons

First, let’s add buttons to change the mood directly:

💡 The createButton() function creates an HTML button element. We can set its position relative to the page and define what happens when it’s clicked using mousePressed(), which will call the respective makeHappy, makeNeutral, or makeAngry function to update the mood.

let mood = 50;
let happyButton, neutralButton, angryButton;

function setup() {
  createCanvas(400, 400);
  
  // Create buttons and position them below the canvas
  happyButton = createButton('Make Happy'); // Create 'Make Happy' button
  happyButton.position(20, 420); // Set position

  happyButton.mousePressed(makeHappy); // Set mood on press
  
  neutralButton = createButton('Make Neutral'); // Create 'Make Neutral' button
  neutralButton.position(150, 420); // Set position
  neutralButton.mousePressed(makeNeutral); // Set mood on press
  
  angryButton = createButton('Make Angry'); // Create 'Make Angry' button
  angryButton.position(280, 420); // Set position
  angryButton.mousePressed(makeAngry); // Set mood on press

}

// Function to set the mood to happy
function makeHappy() {
  mood = 80;
}

// Function to set the mood to neutral
function makeNeutral() {
  mood = 50;
}

// Function to set the mood to angry
function makeAngry() {
  mood = 20;
}

function draw() {
  background(255);
  // Draw face based on mood
  if (mood < 30) {
    drawAngryFace();
  } else if (mood < 70) {
    drawNeutralFace();
  } else {
    drawHappyFace();
  }
}
Advanced Topic:Understanding Function References vs Function Calls

Let’s look at three different ways to handle button clicks:

// ❌ This WON'T work - executes immediately
button.mousePressed(makeHappy());  

// ✅ This WILL work - passes function reference
button.mousePressed(makeHappy);    

// ✅ This WILL work - uses arrow function
button.mousePressed(() => makeHappy());  

Real-World Analogy

Think of it like this:

  • makeDinner() means “make dinner now”
  • makeDinner means “here’s the function to make dinner later”
  • () => { makeDinner() } means “here’s a new function that will run makeDinner when called”

Common Patterns

// Pattern 1: Direct function reference
button.mousePressed(makeHappy);

// Pattern 2: Arrow function with no parameters
button.mousePressed(() => {
    mood = 80;
});

// Pattern 3: Arrow function with parameters
button.mousePressed(() => setMood(80));

function setMood(newMood) {
  mood = newMood;
}

All three patterns are valid ways to create callbacks. Choose the one that makes your code most readable and maintainable.

2. Using Sliders

Sliders (also called range inputs) allow for more precise control over values:

💡 createSlider(min, max, startValue) creates a slider with the specified range and starting value.

let moodSlider;
let mood = 50;

function setup() {
  createCanvas(400, 400);
  
  // Create and position slider
  moodSlider = createSlider(0, 100, 50);
  moodSlider.position(20, 420);
  moodSlider.style('width', '360px'); // Set slider width
  
}

function draw() {
  background(255);
  
  // Update mood from slider value
  mood = moodSlider.value();
  
  // Draw face based on mood
  if (mood < 30) {
    drawAngryFace();
  } else if (mood < 70) {
    drawNeutralFace();
  } else {
    drawHappyFace();
  }
  
  // Display current mood value
  textSize(16);
  text('Mood: ' + mood, 20, 460);
}

https://editor.p5js.org/didny/sketches/kJO9gC_ll

3. Smooth Transitions with lerp()

The lerp() function can create smooth animations between values. Let’s make the face transition smoothly when the mood changes:

💡 lerp(start, end, amount) interpolates between two values. The amount parameter (0-1) determines how quickly the transition happens.

let targetMood = 50;  // The mood we want to reach
let currentMood = 50; // The current mood value
let moodSlider;

function setup() {
  createCanvas(400, 400);
  
  moodSlider = createSlider(0, 100, 50);
  moodSlider.position(20, 420);
  moodSlider.style('width', '360px');
}

function draw() {
  background(255);
  
  // Update target mood from slider
  targetMood = moodSlider.value();
  
  // Smoothly transition current mood towards target mood
  currentMood = lerp(currentMood, targetMood, 0.05);
  
  // Draw face based on current mood
  drawFace(currentMood);
  
  // Display mood values
  textSize(16);
  text('Current Mood: ' + nf(currentMood, 0, 1), 20, 460);
  text('Target Mood: ' + targetMood, 20, 480);
}

function drawFace(mood) {
  // Interpolate color between happy (yellow) and angry (red)
  let lerpVal = 1 - mood / 100;
 

  let r = lerp(255, 255, lerpVal);
  let g = lerp(255, 80, lerpVal);
  let b = lerp(0, 0, lerpVal);
  
  fill(r, g, b);
  strokeWeight(15);
  stroke(0);
  circle(200, 200, 200);
  
  // Interpolate eye positions and mouth arc based on mood
  let leftEyeX1 = lerp(170, 160, lerpVal);
  let leftEyeX2 = lerp(170, 180, lerpVal);
  let rightEyeX1 = lerp(230, 240, lerpVal);
  let rightEyeX2 = lerp(230, 220, lerpVal);
  let mouthY = lerp(220, 240, lerpVal);
  let mouthStart, mouthEnd;

  line(leftEyeX1, 170, leftEyeX2, 190);
  line(rightEyeX1, 170, rightEyeX2, 190);
  angleMode(DEGREES);
  arc(200, mouthY, 60, 60, mouthStart, mouthEnd);
}

4. Combining UI Elements

💡 Try it yourself!

  1. Add new buttons to interact with the emoji agent
  2. Add more UI controls (color picker, checkboxes, etc.)
  3. Try animating different properties of the face (size, color, position)