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 usingmousePressed()
, which will call the respectivemakeHappy
,makeNeutral
, ormakeAngry
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!
- Add new buttons to interact with the emoji agent
- Add more UI controls (color picker, checkboxes, etc.)
- Try animating different properties of the face (size, color, position)