AI Fake Loading Bar - Absurd Excuses Generator

This sketch creates a satirical loading bar that appears to load forever, crawling up to about 87% before slowing to a near-standstill and occasionally slipping backwards. A rotating list of increasingly absurd excuse messages plays underneath, and a 'Cancel' button that mockingly relabels itself 'Nice try' when clicked completes the joke about broken software UX.

🧪 Try This!

Experiment with the code by making these changes:

  1. Speed up the crawl — Increasing the tiny post-stall increment makes the bar creep forward noticeably faster after 87%, softening the joke slightly.
  2. Make excuses flash by — Lowering the message interval makes the excuses cycle much faster, creating a chaotic, rapid-fire effect.
  3. Recolor the bar — Changing the fill color of the inner rectangle instantly swaps the progress bar's color scheme.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch builds a fake progress bar that races to 87% and then stalls, teasing the user with tiny crawling increments, occasional backward slips, and an ever-rotating list of ridiculous excuse messages like 'Bribing the CPU...' and 'Consulting ancient scrolls...'. It's a great example of using simple math tricks - conditional speed changes, modulo timing, and map() - to fake a believable (and hilarious) sense of progress. It also mixes p5.js canvas drawing with a real HTML DOM button created via createButton(), showing how p5 sketches can combine graphics with interactive page elements.

The code is compact: setup() prepares the canvas and the Cancel button, draw() runs the progress and message logic every frame, and windowResized() keeps everything centered if the browser window changes size. By studying it, you'll learn how to fake non-linear animation with branching increments, how to use millis() to time events without blocking the draw loop, and how to style and control DOM elements from inside a p5.js sketch.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas, centers text alignment, and creates a red 'Cancel' button positioned below where the progress bar will be drawn.
  2. Every frame, draw() clears the background and updates the progress variable p: while p is under 87 it climbs quickly, but once past 87 it crawls up by a tiny 0.001 per frame instead.
  3. Every 300 frames, the progress bar deliberately loses a random chunk of its value (but never dips below 80%), creating that maddening 'it went backwards' loading bar effect.
  4. The bar itself is drawn as two rectangles - a gray background bar and a pink foreground bar whose width is calculated with map() based on the current progress percentage.
  5. A separate timer (using millis()) swaps the excuse message every 2.5 seconds by advancing an index into the messages array, wrapping back to the start once it reaches the end.
  6. Clicking the Cancel button doesn't cancel anything - it just relabels itself 'Nice try' for two seconds using setTimeout(), before reverting back to 'Cancel', reinforcing the joke.

🎓 Concepts You'll Learn

Conditional animation speedmillis() based timingmap() for value scalingModulo for cycling through an arrayDOM elements with createButton()setTimeout for delayed UI changeswindowResized for responsive layout

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. Here it prepares the canvas, text settings, and creates and styles a real DOM button using p5's createButton(), position(), size(), and style() methods - showing how p5.js sketches can mix canvas drawing with regular HTML elements.

function setup() {
  createCanvas(windowWidth, windowHeight); textAlign(CENTER, CENTER); textSize(24); noStroke();
  cB = createButton('Cancel'); cB.position(width/2-50, height/2+100); cB.size(100, 40);
  cB.style('background-color','#f44').style('color','white').style('border','none').style('border-radius','5px').style('cursor','pointer');
  cB.mousePressed(() => { cB.html('Nice try'); setTimeout(() => cB.html('Cancel'), 2000); });
}
Line-by-line explanation (9 lines)
createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window, using p5's built-in windowWidth and windowHeight variables.
textAlign(CENTER, CENTER);
Sets text to be drawn centered both horizontally and vertically around the x, y coordinates you give text().
textSize(24);
Sets a default font size of 24 pixels for any text drawn without a more specific textSize() call.
noStroke();
Turns off outlines on shapes so the rectangles drawn later have clean, flat edges.
cB = createButton('Cancel');
Creates a real HTML <button> element labeled 'Cancel' and stores a reference to it in the global variable cB.
cB.position(width/2-50, height/2+100);
Positions the button 50 pixels left of center horizontally and 100 pixels below the vertical center, placing it just under the progress bar and message text.
cB.size(100, 40);
Sets the button's width to 100px and height to 40px.
cB.style('background-color','#f44').style('color','white').style('border','none').style('border-radius','5px').style('cursor','pointer');
Chains multiple CSS style calls to make the button red with white text, no border, rounded corners, and a pointer cursor on hover.
cB.mousePressed(() => { cB.html('Nice try'); setTimeout(() => cB.html('Cancel'), 2000); });
Attaches a click handler: when clicked, the button's text changes to 'Nice try', and after 2000 milliseconds (2 seconds) a setTimeout callback changes it back to 'Cancel'.

draw()

draw() runs continuously (about 60 times per second) and is where all the frame-by-frame logic lives: updating the fake progress value, drawing the bar with map() to scale percentages into pixels, and using millis() to time message changes independently of the frame rate.

🔬 This single line controls the entire fake-loading illusion. What happens if you remove the else branch's backward slip entirely by changing max(p-random(1,5), 80) to just p (so it never goes backwards)?

  if (p < 87) p += 0.05; else { p += 0.001; if (frameCount%300===0) p = max(p-random(1,5), 80); } // Progress logic

🔬 This block advances to the next excuse message once mI milliseconds have passed. What happens if you replace (cM+1)%m.length with floor(random(m.length)) so messages appear in random order instead of sequential?

  if (millis()-mT > mI) { cM = (cM+1)%m.length; mT = millis(); } // Message timing
function draw() {
  background(50); // Dark gray
  if (p < 87) p += 0.05; else { p += 0.001; if (frameCount%300===0) p = max(p-random(1,5), 80); } // Progress logic
  let bW = width*0.6, bH = 40, bX = width/2-bW/2, bY = height/2-bH/2; // Bar dimensions
  fill(80); rect(bX, bY, bW, bH, 5); // Outer bar
  fill(255,0,100); rect(bX+2, bY+2, map(p,0,100,0,bW-4), bH-4, 3); // Inner progress bar
  fill(255); textSize(16); text(floor(p)+'%', bX+bW/2, bY+bH/2); // Percentage text
  if (millis()-mT > mI) { cM = (cM+1)%m.length; mT = millis(); } // Message timing
  textSize(20); text(m[cM], width/2, height/2+60); // Excuse message
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

conditional Progress Speed Logic if (p < 87) p += 0.05; else { p += 0.001; if (frameCount%300===0) p = max(p-random(1,5), 80); } // Progress logic

Fills the bar quickly at first, then slows to a crawl after 87% and occasionally yanks progress backwards to torment the viewer.

conditional Message Rotation Timer if (millis()-mT > mI) { cM = (cM+1)%m.length; mT = millis(); } // Message timing

Checks whether enough real time has passed to switch to the next excuse message, using the modulo operator to wrap back to the first message after the last.

background(50); // Dark gray
Repaints the entire canvas dark gray every frame, erasing the previous frame's drawing so nothing smears or trails.
if (p < 87) p += 0.05; else { p += 0.001; if (frameCount%300===0) p = max(p-random(1,5), 80); } // Progress logic
While progress p is below 87%, it increases quickly (0.05 per frame). Once past 87%, it barely increases (0.001 per frame), and every 300 frames it also randomly subtracts 1-5 percent (never dropping below 80%), creating the frustrating stall-and-slip effect.
let bW = width*0.6, bH = 40, bX = width/2-bW/2, bY = height/2-bH/2; // Bar dimensions
Calculates the progress bar's width (60% of canvas width), fixed height of 40px, and top-left x/y coordinates so the bar is centered on screen.
fill(80); rect(bX, bY, bW, bH, 5); // Outer bar
Draws a medium-gray rounded rectangle as the bar's background/track.
fill(255,0,100); rect(bX+2, bY+2, map(p,0,100,0,bW-4), bH-4, 3); // Inner progress bar
Draws the pink filled portion of the bar, slightly inset from the outer bar; map() converts the 0-100 percentage into an actual pixel width between 0 and bW-4.
fill(255); textSize(16); text(floor(p)+'%', bX+bW/2, bY+bH/2); // Percentage text
Draws the current whole-number percentage (using floor() to drop decimals) centered on top of the bar in white text.
if (millis()-mT > mI) { cM = (cM+1)%m.length; mT = millis(); } // Message timing
Compares the time elapsed since the last message change (millis()-mT) to the interval mI; if enough time has passed, it advances to the next message index (wrapping around with modulo) and resets the timer.
textSize(20); text(m[cM], width/2, height/2+60); // Excuse message
Draws the current excuse message from the array m, centered below the progress bar.

windowResized()

windowResized() is a special p5.js function that automatically runs whenever the browser window changes size. It's essential for responsive sketches - without it, the canvas would stay a fixed size while the window around it changed, and DOM elements like buttons would drift out of alignment.

function windowResized() { resizeCanvas(windowWidth, windowHeight); cB.position(width/2-50, height/2+100); }
Line-by-line explanation (2 lines)
resizeCanvas(windowWidth, windowHeight);
Resizes the canvas to match the browser window's new dimensions whenever the window is resized.
cB.position(width/2-50, height/2+100);
Re-centers the Cancel button based on the new canvas width and height, keeping it aligned under the progress bar after a resize.

📦 Key Variables

p number

Stores the current fake loading progress percentage (0-100), updated every frame with non-linear, stalling logic.

let p = 0;
m array

Holds the list of absurd excuse strings that are cycled through and displayed under the progress bar.

let m = ['Reticulating splines...', 'Convincing electrons...'];
cM number

The current index into the messages array m, indicating which excuse is currently displayed.

let cM = 0;
mT number

Stores the millis() timestamp of the last message change, used to time when to switch to the next excuse.

let mT = 0;
mI number

The interval in milliseconds between excuse message changes.

let mI = 2500;
cB object

A reference to the p5.Element created by createButton(), used to position, style, and attach click behavior to the Cancel button.

let cB;

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

BUG style.css button rule combined with setup()'s cB.position() call

The external CSS rule targeting 'button' sets 'transform: translate(-50%, 50%)' on top of position:absolute/top:50%/left:50%, while setup() also explicitly repositions the button with cB.position(width/2-50, height/2+100). Because p5's position() writes inline left/top styles (which override the CSS top/left), but does NOT override the CSS transform property, the leftover translate(-50%, 50%) can still shift the button unexpectedly from where the JS intends it to be.

💡 Either remove the 'top', 'left', and 'transform' rules from the CSS 'button' selector since positioning is fully handled in JavaScript, or remove the redundant cB.position() calls and rely purely on CSS for placement - don't do both.

STYLE draw()

Magic numbers like 87, 0.05, 0.001, 300, and 80 are hard-coded directly in the progress logic line, making it hard to tell at a glance what they control or to tune them later.

💡 Extract them into named constants such as let stallAt = 87, fastSpeed = 0.05, crawlSpeed = 0.001, resetEvery = 300, resetFloor = 80; and reference those names in the conditional for much better readability.

FEATURE draw() message rotation

Messages always cycle in the same fixed order, so repeat viewers will quickly predict what comes next, reducing the surprise/humor.

💡 Shuffle the m array once in setup() with a Fisher-Yates shuffle, or pick a random non-repeating index each time (e.g. avoid picking the same index twice in a row) so the excuse order feels less predictable.

PERFORMANCE setup()

The button's inline styles are set with five chained .style() calls, each of which is a small individual DOM mutation.

💡 Combine all styles into a single .style('background-color:#f44;color:white;border:none;border-radius:5px;cursor:pointer;') call, or better yet define a CSS class in style.css and apply it with cB.addClass(), reducing repeated DOM writes.

🔄 Code Flow

Code flow showing setup, draw, windowresized

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> progresslogic[Progress Speed Logic] draw --> messagetiming[Message Rotation Timer] progresslogic --> fillbar[Fill Bar Logic] fillbar --> slowprogress[Slow Progress Logic] slowprogress --> yankback[Occasional Backward Progress] messagetiming --> checktime[Check Time for Message Change] checktime --> switchmessage[Switch to Next Excuse Message] switchmessage --> draw click setup href "#fn-setup" click draw href "#fn-draw" click progresslogic href "#sub-progress-logic" click messagetiming href "#sub-message-timing" click fillbar href "#sub-fill-bar-logic" click slowprogress href "#sub-slow-progress-logic" click yankback href "#sub-occasional-backward-progress" click checktime href "#sub-check-time" click switchmessage href "#sub-switch-message"

❓ Frequently Asked Questions

What visual elements are featured in the AI Fake Loading Bar sketch?

The sketch displays a humorous fake loading bar that visually progresses while showing absurd excuse messages, set against a dark gray background.

How can users interact with the AI Fake Loading Bar sketch?

Users can click the 'Cancel' button, which humorously changes the text to 'Nice try' temporarily, emphasizing the satirical nature of the loading experience.

What creative coding concept is showcased in the AI Fake Loading Bar sketch?

This sketch demonstrates the use of dynamic text and animated progress bars to create a playful commentary on user experience frustrations in software.

Preview

AI Fake Loading Bar - Absurd Excuses Generator - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of AI Fake Loading Bar - Absurd Excuses Generator - Code flow showing setup, draw, windowresized
Code Flow Diagram