AI Stopwatch Timer - Clean Start/Stop Utility

This sketch creates a functional digital stopwatch with a large display showing minutes, seconds, and centiseconds. Users can click a green START button to begin timing and a red STOP button to pause, with the elapsed time persisting when paused. The interface is clean and responsive to window resizing.

๐ŸŽ“ Concepts You'll Learn

Animation loopTime tracking with millis()Mouse interactionConditional logicText formattingButton collision detectionState managementResponsive canvas

๐Ÿ”„ Code Flow

Code flow showing setup, draw, mousepressed, windowresized

๐Ÿ’ก Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> time-calculation[Time Calculation] draw --> time-conversion[Time Unit Conversion] draw --> display-timer[Timer Display] draw --> button-setup[Button Dimensions] draw --> initial-message[Initial Instruction Message] draw --> start-button[start-button] draw --> stop-button[stop-button] time-calculation -->|if running| current-time[Current Time - Start Time] time-calculation -->|if paused| stored-elapsed[Stored Elapsed Value] time-calculation --> time-conversion time-conversion --> display-timer display-timer -->|format MM:SS.CS| formatted-time[Formatted Time] button-setup --> start-button button-setup --> stop-button start-button -->|draw green button| start-button-click[start-button-click] stop-button -->|draw red button| stop-button-click[stop-button-click] initial-message -->|if not started| display-instruction[Display Instruction] start-button-click -->|if not running| start-timer[Start Timer] stop-button-click -->|if running| pause-timer[Pause Timer] click setup href "#fn-setup" click draw href "#fn-draw" click time-calculation href "#sub-time-calculation" click time-conversion href "#sub-time-conversion" click display-timer href "#sub-display-timer" click button-setup href "#sub-button-setup" click start-button href "#sub-start-button" click stop-button href "#sub-stop-button" click initial-message href "#sub-initial-message" click start-button-click href "#sub-start-button-click" click stop-button-click href "#sub-stop-button-click" click current-time href "#sub-current-time" click stored-elapsed href "#sub-stored-elapsed" click formatted-time href "#sub-formatted-time" click display-instruction href "#sub-display-instruction" click start-timer href "#sub-start-timer" click pause-timer href "#sub-pause-timer"

๐Ÿ“ Code Breakdown

setup()

setup() runs once when the sketch starts. Here we initialize the canvas size and configure text settings that will be used throughout the sketch.

function setup() {
  createCanvas(windowWidth, windowHeight);
  textAlign(CENTER, CENTER);
  textFont('monospace');
}

Line by Line:

createCanvas(windowWidth, windowHeight)
Creates a canvas that fills the entire browser window, making the stopwatch responsive to screen size
textAlign(CENTER, CENTER)
Centers all text both horizontally and vertically, so text appears exactly where you specify
textFont('monospace')
Sets the font to monospace (fixed-width), which makes the digital display look like a classic timer

draw()

draw() runs 60 times per second, continuously updating the display. The key insight is that time is calculated differently based on state: when running, we use the current time minus the start time; when paused, we use the previously stored elapsed value. This allows the timer to pause and resume correctly.

function draw() {
  background(255);
  let t = running ? millis() - startTime : elapsed;
  let m=floor(t/60000),s=floor(t/1000)%60,cs=floor((t%1000)/10);
  textSize(72);
  fill(0);
  text(nf(m,2)+":"+nf(s,2)+"."+nf(cs,2), width/2, height/2-40);
  let bw=140,bh=50,y=height/2+40,off=90;
  rectMode(CENTER);
  fill(0,200,0);
  rect(width/2-off,y,bw,bh,8);
  fill(255);
  textSize(20);
  text("START",width/2-off,y);
  fill(200,0,0);
  rect(width/2+off,y,bw,bh,8);
  fill(255);
  text("STOP",width/2+off,y);
  if(!running&&elapsed===0){
    fill(0);
    textSize(18);
    text("Click START to begin",width/2,y+60);
  }
}

๐Ÿ”ง Subcomponents:

calculation Time Calculation let t = running ? millis() - startTime : elapsed

Determines elapsed time: if running, calculates from current time minus start time; if paused, uses the stored elapsed value

calculation Time Unit Conversion let m=floor(t/60000),s=floor(t/1000)%60,cs=floor((t%1000)/10)

Converts milliseconds to minutes, seconds, and centiseconds for display

calculation Timer Display text(nf(m,2)+":"+nf(s,2)+"."+nf(cs,2), width/2, height/2-40)

Formats and displays the time with zero-padding (nf function) in MM:SS.CS format

calculation Button Dimensions let bw=140,bh=50,y=height/2+40,off=90

Defines button width, height, vertical position, and horizontal offset from center for consistent button placement

calculation START Button Drawing fill(0,200,0); rect(width/2-off,y,bw,bh,8); fill(255); textSize(20); text("START",width/2-off,y)

Draws the green START button with white text, positioned to the left of center

calculation STOP Button Drawing fill(200,0,0); rect(width/2+off,y,bw,bh,8); fill(255); text("STOP",width/2+off,y)

Draws the red STOP button with white text, positioned to the right of center

conditional Initial Instruction Message if(!running&&elapsed===0){ fill(0); textSize(18); text("Click START to begin",width/2,y+60); }

Displays helpful instruction text only when the timer hasn't been started yet

Line by Line:

background(255)
Clears the canvas with white color each frame, erasing the previous frame's content
let t = running ? millis() - startTime : elapsed
Calculates elapsed time: if timer is running, subtract the start time from current time; otherwise use the stored elapsed value
let m=floor(t/60000),s=floor(t/1000)%60,cs=floor((t%1000)/10)
Converts milliseconds to display units: m = minutes (divide by 60000), s = seconds mod 60 (divide by 1000, keep remainder), cs = centiseconds (remainder of 1000 divided by 10)
textSize(72)
Sets the font size to 72 pixels for the large timer display
fill(0)
Sets the fill color to black (RGB: 0,0,0) for the timer text
text(nf(m,2)+":"+nf(s,2)+"."+nf(cs,2), width/2, height/2-40)
Displays the formatted time: nf(value, digits) pads numbers with leading zeros, creating MM:SS.CS format at the center-top of buttons
let bw=140,bh=50,y=height/2+40,off=90
Defines button dimensions and positions: width=140, height=50, vertical position 40 pixels below center, horizontal offset 90 pixels from center
rectMode(CENTER)
Changes rectangle drawing mode so coordinates specify the center point instead of the top-left corner
fill(0,200,0)
Sets fill color to green (RGB: 0,200,0) for the START button
rect(width/2-off,y,bw,bh,8)
Draws the green START button: centered at (width/2-90, height/2+40), size 140x50, with 8-pixel rounded corners
fill(255)
Sets fill color to white for button text
textSize(20)
Sets font size to 20 pixels for button labels
text("START",width/2-off,y)
Displays the word START centered on the green button
fill(200,0,0)
Sets fill color to red (RGB: 200,0,0) for the STOP button
rect(width/2+off,y,bw,bh,8)
Draws the red STOP button: centered at (width/2+90, height/2+40), same size and rounded corners as START
text("STOP",width/2+off,y)
Displays the word STOP centered on the red button
if(!running&&elapsed===0)
Checks if timer is not running AND elapsed time is zero (timer hasn't been started yet)
text("Click START to begin",width/2,y+60)
Shows instruction text 60 pixels below the buttons only on first load

mousePressed()

mousePressed() is called once whenever the user clicks the mouse. This function implements button collision detection by checking if the mouse coordinates fall within the rectangular bounds of each button. The START button only works when not running, and STOP only works when running, preventing invalid state transitions.

function mousePressed() {
  let bw=140,bh=50,y=height/2+40,off=90;
  if(!running&&mouseX>width/2-off-bw/2&&mouseX<width/2-off+bw/2&&
     mouseY>y-bh/2&&mouseY<y+bh/2){running=true;startTime=millis()-elapsed;}
  if(running&&mouseX>width/2+off-bw/2&&mouseX<width/2+off+bw/2&&
     mouseY>y-bh/2&&mouseY<y+bh/2){running=false;elapsed=millis()-startTime;}
}

๐Ÿ”ง Subcomponents:

conditional START Button Click Detection if(!running&&mouseX>width/2-off-bw/2&&mouseX<width/2-off+bw/2&&mouseY>y-bh/2&&mouseY<y+bh/2){running=true;startTime=millis()-elapsed;}

Detects clicks on the START button when timer is not running, then starts the timer while preserving any previous elapsed time

conditional STOP Button Click Detection if(running&&mouseX>width/2+off-bw/2&&mouseX<width/2+off+bw/2&&mouseY>y-bh/2&&mouseY<y+bh/2){running=false;elapsed=millis()-startTime;}

Detects clicks on the STOP button when timer is running, then pauses the timer and stores the elapsed time

Line by Line:

let bw=140,bh=50,y=height/2+40,off=90
Redefines button dimensions to match those in draw() - necessary for accurate collision detection
if(!running&&mouseX>width/2-off-bw/2&&mouseX<width/2-off+bw/2&&mouseY>y-bh/2&&mouseY<y+bh/2)
Checks if timer is NOT running AND mouse click is within START button bounds (left edge to right edge, top to bottom)
running=true;startTime=millis()-elapsed
Starts the timer by setting running to true, and sets startTime to current time minus any previously elapsed time, allowing resume functionality
if(running&&mouseX>width/2+off-bw/2&&mouseX<width/2+off+bw/2&&mouseY>y-bh/2&&mouseY<y+bh/2)
Checks if timer IS running AND mouse click is within STOP button bounds
running=false;elapsed=millis()-startTime
Stops the timer by setting running to false, and calculates the total elapsed time from the current moment minus when it started

windowResized()

windowResized() is a special p5.js function that automatically triggers whenever the browser window is resized. By calling resizeCanvas(), we ensure the stopwatch always fills the screen and buttons stay centered regardless of screen size.

function windowResized(){resizeCanvas(windowWidth,windowHeight);}

Line by Line:

resizeCanvas(windowWidth,windowHeight)
Automatically resizes the p5.js canvas to match the current browser window dimensions whenever the window is resized

๐Ÿ“ฆ Key Variables

running boolean

Tracks whether the stopwatch is currently timing (true) or paused (false). Controls whether elapsed time increases or stays constant.

let running = false;
startTime number

Stores the millisecond timestamp when the timer was started or resumed. Used to calculate elapsed time by subtracting from current time.

let startTime = 0;
elapsed number

Stores the total elapsed milliseconds when the timer is paused. Allows the timer to resume from where it stopped instead of resetting.

let elapsed = 0;
t number

Temporary variable in draw() that holds the current total elapsed time in milliseconds, calculated based on whether the timer is running or paused.

let t = running ? millis() - startTime : elapsed;
m number

Stores the minutes component of the elapsed time, calculated by dividing total milliseconds by 60000 and flooring the result.

let m = floor(t/60000);
s number

Stores the seconds component (0-59) of the elapsed time, calculated by dividing by 1000 and using modulo 60 to get remainder.

let s = floor(t/1000)%60;
cs number

Stores the centiseconds component (0-99) of the elapsed time, calculated from the remainder milliseconds divided by 10.

let cs = floor((t%1000)/10);
bw number

Button width in pixels. Used in both draw() and mousePressed() to define and detect button boundaries.

let bw = 140;
bh number

Button height in pixels. Used in both draw() and mousePressed() to define and detect button boundaries.

let bh = 50;
y number

Vertical position of both buttons on the canvas. Positioned 40 pixels below the vertical center.

let y = height/2+40;
off number

Horizontal offset of buttons from center. START button is 90 pixels left of center, STOP is 90 pixels right.

let off = 90;

๐Ÿงช Try This!

Experiment with the code by making these changes:

  1. Change the button colors by modifying the RGB values in fill(0,200,0) for START and fill(200,0,0) for STOP. Try fill(0,0,200) for blue buttons.
  2. Increase the timer display size by changing textSize(72) to textSize(100) or higher to make the numbers bigger.
  3. Modify the button dimensions by changing bw=140 to bw=200 and bh=50 to bh=80 to create larger, easier-to-click buttons.
  4. Change the timer format by modifying the text display line - try adding hours by calculating let h=floor(t/3600000) and displaying it as h+":"+nf(m,2)+":"+nf(s,2).
  5. Add a RESET button by creating a third button at width/2 (center) and adding logic in mousePressed() to set running=false and elapsed=0 when clicked.
  6. Experiment with the instruction text by changing "Click START to begin" to a different message, or add additional instructions.
Open in Editor & Experiment โ†’

๐Ÿ”ง Potential Improvements

Here are some ways this code could be enhanced:

BUG mousePressed() - both button click handlers

Button dimensions (bw, bh, y, off) are redefined in mousePressed() instead of being shared with draw(). If these values ever change in one function but not the other, button clicks won't align with visual buttons.

๐Ÿ’ก Define bw, bh, y, and off as global constants at the top of the sketch: const BUTTON_WIDTH = 140; const BUTTON_HEIGHT = 50; const BUTTON_Y_OFFSET = 40; const BUTTON_H_OFFSET = 90; Then use these in both functions.

BUG mousePressed() - elapsed time calculation

When STOP is clicked, elapsed is set to millis()-startTime, but startTime was already adjusted by elapsed when START was clicked. This causes the elapsed time to accumulate incorrectly on subsequent pauses.

๐Ÿ’ก Change the STOP button logic to: elapsed += millis() - startTime; This adds the new interval to the existing elapsed time instead of replacing it.

STYLE sketch.js - variable declarations

Global variables are declared on one line with commas (let running = false, startTime = 0, elapsed = 0;) which reduces readability.

๐Ÿ’ก Separate them for clarity: let running = false; let startTime = 0; let elapsed = 0; This makes each variable's purpose clearer.

FEATURE mousePressed()

No visual feedback when buttons are clicked - users might not know if their click registered.

๐Ÿ’ก Add a brief visual effect like changing button color on click, or add a 'reset' button to allow clearing the timer and starting fresh from zero.

PERFORMANCE draw() - button drawing code

Button positions and dimensions are recalculated every frame even though they never change. The same calculations happen again in mousePressed().

๐Ÿ’ก Move button dimension calculations to setup() as constants, or create a helper function that returns button bounds to avoid duplication.

Preview

AI Stopwatch Timer - Clean Start/Stop Utility - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of AI Stopwatch Timer - Clean Start/Stop Utility - Code flow showing setup, draw, mousepressed, windowresized
Code Flow Diagram