IrishWristWatchScrable

This sketch takes the tongue-twister 'I wish to wash my Irish wristwatch.' and shatters it into individual letters, scattering each one to a random spot on the canvas. The result looks like the phrase exploded and froze mid-air, with letters floating independently across the screen.

🧪 Try This!

Experiment with the code by making these changes:

  1. Change the phrase — Replace the tongue-twister with your own sentence to scatter different text across the canvas.
  2. Colorize the letters — Give each character a random color instead of solid black by setting a random fill inside the draw loop.
  3. Reshuffle on click — Add a mousePressed() function so clicking the canvas scatters the letters into brand new random positions.
  4. Darken the background — Switch to a dark background and light text for a completely different mood.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch takes a full sentence, breaks it apart into individual characters using JavaScript's split() method, and then places every single letter at its own random x/y coordinate on the canvas using p5.Vector. Instead of reading the tongue-twister in order, the viewer sees a scattered field of text where letters, spaces, and punctuation drift independently across the screen. It's a simple but effective demonstration of how p5.js treats text as drawable shapes just like circles or rectangles, and how random() plus createVector() can turn an array of data into a scattered layout.

The code is organized around three global arrays and a small set of functions: setup() prepares the canvas and splits the phrase into a characters array, generatePositions() assigns each character a random p5.Vector position, and draw() loops through every character and draws it at its stored position each frame. By studying this sketch you'll learn how to convert a string into an array of characters, how vectors can store paired x/y coordinates, and how separating 'position generation' from 'drawing' makes it easy to regenerate a layout (for example on window resize) without touching the drawing logic.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas, configures text styling (size, alignment, color), and splits the phrase string into an array of individual characters called chars.
  2. generatePositions() then runs once, looping through every character and pushing a new p5.Vector with random x and y coordinates into the positions array - so each letter gets its own random home on the screen.
  3. Every frame, draw() clears the background and loops through the chars array, drawing each character at its matching position from the positions array using the text() function.
  4. Because the positions are only generated once (not re-randomized every frame), the scattered layout stays perfectly still - the letters appear frozen in place rather than continuously jittering.
  5. If the browser window is resized, windowResized() fires automatically, resizing the canvas to match the new window dimensions and calling generatePositions() again to scatter the letters fresh across the new canvas size.

🎓 Concepts You'll Learn

String manipulation (split)Arrays of objectsp5.Vector for storing coordinatesRandom positioningText rendering with text()Responsive canvas with windowResized()

📝 Code Breakdown

setup()

setup() runs once when the sketch starts, making it the right place to configure drawing settings and prepare data (like splitting a string) before the draw loop begins animating.

function setup() {
  createCanvas(windowWidth, windowHeight);
  textSize(32);
  textAlign(CENTER, CENTER);
  noStroke();
  fill(0);

  // Split phrase into individual characters (including spaces and punctuation)
  chars = phrase.split("");
  generatePositions();
}
Line-by-line explanation (7 lines)
createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window using p5's built-in windowWidth and windowHeight variables.
textSize(32);
Sets the font size for all text drawn afterward to 32 pixels.
textAlign(CENTER, CENTER);
Makes text() draw each character centered on its x/y coordinate both horizontally and vertically, so the position you give is the character's true center point.
noStroke();
Turns off outlines so shapes and text don't get an unwanted border.
fill(0);
Sets the fill color to black (0 = black in grayscale mode), which is the color the text will be drawn in.
chars = phrase.split("");
Splits the phrase string into an array where every single character (letters, spaces, and punctuation) becomes its own array element.
generatePositions();
Calls the helper function that assigns a random position to each character, filling the positions array.

generatePositions()

This function separates 'where things go' from 'how things are drawn', a common and useful pattern - it can be called again anytime (like after a window resize) to produce a fresh layout without touching the draw() function at all.

🔬 This loop gives every character a fully random spot anywhere on the canvas. What happens if you change random(height) to random(height / 2), so positions are only picked from the top half?

  for (let i = 0; i < chars.length; i++) {
    positions.push(createVector(random(width), random(height)));
  }
function generatePositions() {
  positions = [];
  for (let i = 0; i < chars.length; i++) {
    positions.push(createVector(random(width), random(height)));
  }
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

for-loop Random Position Assignment Loop for (let i = 0; i < chars.length; i++) { positions.push(createVector(random(width), random(height))); }

Walks through every character in the phrase and gives it a brand new random x/y position stored as a p5.Vector.

positions = [];
Resets the positions array to empty so old positions don't pile up if this function is called again (e.g. on resize).
for (let i = 0; i < chars.length; i++) {
Loops once for every character in the chars array, so each letter gets exactly one position.
positions.push(createVector(random(width), random(height)));
createVector() bundles an x and y value into one object; random(width) and random(height) pick a random point anywhere inside the canvas, and push() adds that vector to the positions array.

draw()

draw() runs continuously (about 60 times per second) and is where all visible drawing happens. Here it simply redraws the same static positions every frame - the scatter looks 'frozen' because positions never change inside draw().

🔬 This loop draws every letter frozen at its stored position. What happens if you add positions[i].x += random(-1, 1) inside the loop before the text() call, so each letter jitters slightly every frame?

  for (let i = 0; i < chars.length; i++) {
    // Spaces will just show as blanks, which is fine
    text(chars[i], positions[i].x, positions[i].y);
  }
function draw() {
  background(240);

  for (let i = 0; i < chars.length; i++) {
    // Spaces will just show as blanks, which is fine
    text(chars[i], positions[i].x, positions[i].y);
  }
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

for-loop Draw Each Character Loop for (let i = 0; i < chars.length; i++) { text(chars[i], positions[i].x, positions[i].y); }

Draws every character from the chars array at its matching stored position.

background(240);
Fills the entire canvas with a light gray (240) color every frame, clearing whatever was drawn the frame before.
for (let i = 0; i < chars.length; i++) {
Loops through every character in the phrase, one at a time, using i as the index.
text(chars[i], positions[i].x, positions[i].y);
Draws the character at index i using the x and y coordinates stored in the matching p5.Vector at positions[i].

windowResized()

windowResized() is a special p5.js event function, automatically invoked whenever the browser window changes size. Handling it keeps responsive sketches looking correct instead of leaving blank space or cut-off content.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  // Re-randomize positions when the window size changes
  generatePositions();
}
Line-by-line explanation (2 lines)
resizeCanvas(windowWidth, windowHeight);
p5.js automatically calls this function whenever the browser window is resized; this line resizes the canvas element to match the new window dimensions.
generatePositions();
Regenerates all random positions so the letters spread across the newly sized canvas instead of staying clustered in the old (possibly smaller) area.

📦 Key Variables

phrase string

Stores the full tongue-twister sentence that will be broken apart into individual characters.

let phrase = "I wish to wash my Irish wristwatch.";
chars array

Holds every individual character (letters, spaces, punctuation) from the phrase, produced by splitting the string.

let chars = [];
positions array

Holds a p5.Vector for every character in chars, storing the random x/y coordinate where that character will be drawn.

let positions = [];

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

FEATURE draw()

The letter positions never change after generatePositions() runs, so the sketch is essentially a static image redrawn every frame - there's no animation despite running inside draw().

💡 Add subtle continuous motion, such as nudging each position vector slightly with noise() or random() every frame, so the letters gently drift instead of staying perfectly frozen.

PERFORMANCE draw()

Since nothing changes frame to frame, calling background() and re-drawing every character 60 times per second wastes CPU/GPU cycles for a static result.

💡 Either add real animation to justify the continuous redraw, or draw the scattered text once in setup() (or to an offscreen graphics buffer) and call noLoop() to stop draw() from running repeatedly.

STYLE global variables / fill(0)

The text color is hardcoded to black via fill(0) in setup() with no easy way to adjust it, and there's no variable controlling color, making palette experiments harder than they need to be.

💡 Introduce a global textColor variable (e.g. let textColor = 0;) set in setup() with fill(textColor), so learners have one obvious place to tweak the color.

BUG generatePositions()

Positions are random anywhere in [0, width] and [0, height], but textAlign(CENTER, CENTER) means letters near the very edge can be clipped in half since text can be drawn partially off-canvas.

💡 Inset the random range slightly, e.g. random(20, width - 20) and random(20, height - 20), so no character gets cut off at the canvas boundary.

🔄 Code Flow

Code flow showing setup, generatepositions, draw, windowresized

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

graph TD start[Start] --> setup[setup] setup --> generatepositions[generatepositions] setup --> draw[draw loop] draw --> positionloop[position-loop] positionloop --> drawcharsloop[draw-chars-loop] drawcharsloop --> draw draw --> draw windowresized --> generatepositions click setup href "#fn-setup" click generatepositions href "#fn-generatepositions" click draw href "#fn-draw" click positionloop href "#sub-position-loop" click drawcharsloop href "#sub-draw-chars-loop"

Preview

IrishWristWatchScrable - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of IrishWristWatchScrable - Code flow showing setup, generatepositions, draw, windowresized
Code Flow Diagram