Skip to content

Introduction to Creative Coding & Canvas Foundations

Summary

Introduces the p5.js canvas environment, the setup() and draw() lifecycle loops, coordinate grids, and render cycles. Students will gain practical hands-on experience by building interactive sketches and visual experiments that demonstrate these concepts.

Concepts Covered

This chapter covers the following 33 concepts from the learning graph:

  1. Coordinate System
  2. Canvas Element
  3. Setup Function
  4. Draw Function
  5. Frame Rate
  6. Frame Count
  7. Pixel Grid
  8. Canvas Width
  9. Canvas Height
  10. Create Canvas Function
  11. Background Color Function
  12. Preload Function
  13. No Loop Function
  14. Redraw Function
  15. Loop Control Function
  16. Window Resized Event
  17. Resize Canvas Function
  18. Full Screen Mode
  19. Display Density
  20. Pixel Density Control
  21. Console Log Debugging
  22. Statement Semicolon Syntax
  23. Code Comments Syntax
  24. Script Inclusion Tag
  25. p5.js Library Import
  26. CDN Script Loading
  27. Global Mode Execution
  28. Instance Mode Execution
  29. p5 Constructor Function
  30. Render Cycle Loop
  31. Delta Time Tracking
  32. Target Frame Rate Setting
  33. Canvas Aspect Ratio

Prerequisites

This chapter assumes only the prerequisites listed in the course description.


Welcome to the expansive and liberating world of creative coding! If you've ever looked at a blank piece of paper and felt the thrill of infinite possibilities, you already understand the mindset required for this journey. In traditional fine arts, your mediums are charcoal, watercolor, clay, or oil paints. In creative coding, your medium is logic, math, and light. Your brush is the code you write, and your canvas is the digital screen.

In this chapter, we will establish the foundational mental models needed to bridge visual art and computer programming. We will build your digital studio from the ground up, understand how a computer maps mathematical coordinates to physical pixels on a screen, and learn the essential lifecycle loops that bring static code to animated life.

Welcome to The Art of Processing!

Palette waving welcome Welcome, creators! Have you ever looked at a blank digital screen and wished you could just paint with pure logic? I'm Palette, and in this very first chapter, we are going to build your digital studio from the ground up to bridge visual art with computer programming. Time to color outside the loops!

I'll be popping into the margins all the way through this book, but I do not show up randomly. I have exactly six jobs, and you'll learn to recognize me by which one I'm doing:

  1. Welcome you at the start of every chapter.
  2. Help you think things through when an idea is particularly clever or abstract.
  3. Give you tips — the secret moves a working creative coder uses.
  4. Warn you gently about common mistakes and syntax pitfalls.
  5. Encourage you when a difficult concept might feel overwhelming.
  6. Celebrate with you at the end of each chapter when you've earned it!

Building the Artist's Studio

Before a painter can begin a masterpiece, they must prepare their studio. They set up the easel, stretch the canvas, and lay out their palettes. In web-based creative coding, our studio is the web browser, and our easel is an HTML document.

However, web browsers natively only understand standard JavaScript, which is designed for building forms, handling buttons, and manipulating text. To draw graphics easily, we need a specialized toolkit. This is where the p5.js Library Import comes in. A code library is a collection of pre-written functions that extends the capabilities of the language. The p5.js library was created by artists, for artists, specifically to make drawing and animating on the web intuitive.

To bring this library into our studio, we use a Script Inclusion Tag in our HTML file. Think of this tag as a delivery truck bringing supplies to your door. Rather than downloading the entire p5.js library file to our local computer, we use a technique called CDN Script Loading. CDN stands for Content Delivery Network. It is a system of globally distributed servers. When you use a CDN, your browser fetches the p5.js library from the server closest to you, making the loading process incredibly fast and efficient.

<!DOCTYPE html>
<html>
  <head>
    <!-- Script Inclusion Tag using CDN Script Loading -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.js"></script>
  </head>
  <body>
    <!-- This is where your actual sketch file is loaded -->
    <script src="sketch.js"></script>
  </body>
</html>

The Grammar of Code

When writing instructions for the computer, we must follow strict grammatical rules, known as syntax. In English, we use periods to end sentences. In JavaScript, we use the Statement Semicolon Syntax. Placing a semicolon (;) at the end of a command tells the computer, "I have finished this instruction; please move on to the next one."

While modern JavaScript engines are sometimes forgiving and will try to guess where your semicolons should go (a process called Automatic Semicolon Insertion), relying on the computer's guesswork is a bad habit. As an artist, you want absolute control over your medium. A missing semicolon in a complex mathematical formula can cause bizarre, hard-to-track errors.

Equally important to the code the computer reads is the code the computer ignores. We use Code Comments Syntax to leave notes for ourselves and our collaborators. Comments are an essential storytelling tool in programming. They explain the why behind the what. In JavaScript, any text following two forward slashes (//) on a line is completely ignored by the execution engine.

// This is a single-line comment. 
// Good comments explain intent, not just literal actions.

let x = 10; // The semicolon ends the statement.

The Digital Canvas and the Woven Screen

Traditional artists paint on woven linen or cotton canvas. Creative coders paint on a Canvas Element, a specialized digital drawing area embedded directly inside a webpage.

If you look very closely at a digital screen with a magnifying glass, you won't see continuous brush strokes. Instead, you will see a Pixel Grid—a dense matrix of tiny, discrete light-emitting squares (pixels). Every drawing, photograph, and animation on a computer is an optical illusion created by turning millions of these individual squares on and off at varying brightness levels.

To command this grid, we must be able to specify exact locations. We do this using a Coordinate System.

If you remember graphing from high school algebra, you likely used a Cartesian coordinate system where the origin (0, 0) is in the center, the x-axis goes right, and the y-axis goes up.

Computer graphics work differently. Because early television and computer monitors (CRT displays) rendered images by shooting an electron beam across the screen starting from the top-left corner and scanning left-to-right, top-to-bottom, the digital coordinate system was designed to match this hardware reality.

In p5.js, the origin (0, 0) is located at the top-left corner of the canvas. The x-axis extends to the right (positive x values), and critically, the y-axis extends downward (positive y values).

Diagram: Coordinate System Explorer

Run Coordinate System Explorer Fullscreen

Coordinate System Explorer

Coordinate System Explorer Type: MicroSim sim-id: coordinate-system-explorer
Library: p5.js
Status: Specified

Learning Objective: Understanding (Students will interactively explore how (x, y) coordinates map to the top-left origin canvas).

Visual Elements: - A large grid representing the p5.js canvas. - The origin (0, 0) clearly marked in the top-left corner. - The x-axis running right (labeled with increasing numbers). - The y-axis running down (labeled with increasing numbers). - A draggable point (a distinct circle) on the grid.

Interactivity: - When the user drags the point, its current (x, y) coordinates update in real-time in a large readout panel. - Hovering over quadrants highlights the area and explains the pixel distances from the origin in plain text. - The grid features horizontal and vertical sliders to resize the canvas dynamically, demonstrating how width and height boundary limits shift.

Implementation: p5.js canvas with mouseDragged events to update the coordinate display and text() calls to render the math.

Dimension and Proportion

Before we can paint, we must stretch the canvas to a specific size. The canvas has an exact horizontal span measured in pixels, called the Canvas Width, and a vertical span, called the Canvas Height.

The relationship between these two measurements is known as the Canvas Aspect Ratio. Understanding aspect ratios is vital for visual composition. A 1:1 aspect ratio produces a perfect square (popular in generative art for Instagram). A 16:9 ratio produces a widescreen cinematic rectangle. A 9:16 ratio is tall and vertical, ideal for smartphone screens.

To physically create this drawing space in memory, we invoke the Create Canvas Function (createCanvas()), passing in the desired width and height.

Immediately after creating the canvas, we typically want to prime it with a base layer of paint. We do this using the Background Color Function (background()). Calling background(200) will fill the entire pixel grid with a light gray color, effectively erasing anything that was previously there.

The Lifecycle: Preload, Setup, and Draw

Think of a p5.js program as a theatrical production. A successful play requires preparation before the audience arrives, an opening sequence, and finally, the live performance. In p5.js, this lifecycle is managed by three core functions: preload(), setup(), and draw().

The Preload Function (preload()) is the equivalent of building the sets and gathering the props before the theater doors open. If your artwork relies on large external files—like a high-resolution photograph, a custom typography font file, or a massive audio track—the browser needs time to download them over the internet.

If you try to draw an image before it has fully downloaded, the program will crash. The preload() function solves this. It acts as a strict gatekeeper: the program will absolutely halt and wait until every file requested inside preload() is 100% loaded into memory before allowing the rest of the program to begin.

Once the props are ready, the theater doors open and the Setup Function (setup()) is executed. The crucial rule of setup() is that it runs precisely once. It is the opening curtain. This is where you establish the foundational environment: you call the createCanvas function to define the stage size, you set initial configuration rules (like how thick lines should be drawn), and you might set starting values for your variables.

let myFont;

// The Preload Function runs first and waits for completion
function preload() {
  myFont = loadFont('assets/inconsolata.otf');
}

// The Setup Function runs once
function setup() {
  // Create Canvas Function: 800 pixels wide by 600 pixels high
  createCanvas(800, 600);
}

Immediately after setup() finishes its single run, the live performance begins. The Draw Function (draw()) takes over. Unlike setup(), the draw() function does not run just once. It executes its code from top to bottom, and the millisecond it reaches the end, it immediately jumps back to the top and runs again.

This continuous, infinite loop is called the Render Cycle Loop. Every single pass through the draw() loop represents a single frame of animation. If the draw() function paints a circle slightly further to the right on each successive pass, the human eye perceives it as a circle moving smoothly across the screen.

let circleX = 0;

function draw() {
  // 1. Erase the previous frame with a solid color
  background(220); 

  // 2. Draw a circle at the current circleX position
  circle(circleX, 300, 50); 

  // 3. Update the position for the NEXT time the loop runs
  circleX = circleX + 2; 
}

The Engine of Time: Frame Rates and Deltas

The illusion of motion relies heavily on the speed at which the Render Cycle Loop executes. This speed is measured by the Frame Rate, which is expressed in frames per second (FPS).

Historically, cinematic film runs at 24 FPS, television at 30 FPS, and modern fluid video games and interactive graphics target 60 FPS. By default, p5.js aggressively attempts to run your draw() loop 60 times every single second.

You can artificially limit this speed using the Target Frame Rate Setting (frameRate(fps)). For example, calling frameRate(10) inside your setup() function will force the sketch to intentionally slow down, creating a choppy, stop-motion aesthetic.

As the sketch runs, p5.js maintains an internal tally of exactly how many times the draw() loop has executed since the program started. This running total is stored in the Frame Count system variable (frameCount). frameCount is a wonderful tool for generative art; passing frameCount into a trigonometric math function like sin() creates perfectly smooth, perpetual oscillation.

However, relying solely on frameCount to dictate motion can be dangerous if you are building complex simulations or games.

Imagine you write code that moves a racecar forward by 5 pixels every frame. On your powerful brand-new laptop, the sketch easily hits 60 FPS, meaning the car moves 300 pixels per second (5 * 60). But what if your user opens the sketch on an old, struggling five-year-old smartphone? The phone might only be able to calculate 30 frames per second. On that phone, the car will only move 150 pixels per second. The simulation is broken; time is moving at half speed!

To solve this, professional developers use Delta Time Tracking. The system variable deltaTime records the precise number of milliseconds that have elapsed since the previous frame was rendered. By multiplying your car's speed by deltaTime, the movement becomes time-dependent, rather than frame-dependent. If the frame rate drops, deltaTime goes up, and the car takes a larger mathematical "step" to compensate, ensuring it crosses the screen in the exact same amount of physical time regardless of device horsepower.

Diagram: Render Cycle and Frame Rate Monitor

Run Render Cycle and Frame Rate Monitor Fullscreen

Render Cycle and Frame Rate Monitor

Render Cycle and Frame Rate Monitor Type: MicroSim sim-id: frame-rate-monitor
Library: p5.js
Status: Specified

Learning Objective: Analyzing (Students will observe the relationship between the target frame rate, the actual delta time, and the resulting visual smoothness).

Visual Elements: - Two identical moving objects (like cars) racing across the screen. - Car A moves using frame-dependent logic (x += 5). - Car B moves using time-dependent logic (x += speed * deltaTime). - A real-time line chart tracking frameRate and deltaTime. - A slider to forcefully restrict the Target Frame Rate.

Interactivity: - Users slide the target frame rate down from 60 FPS to 10 FPS. - As the frame rate drops, Car A visibly slows down and loses the race. - Car B begins to stutter visually, but maintains its true velocity across the screen, tying the race despite the low frame rate. - The chart updates dynamically to show the inverse relationship: when the frame rate plummets, deltaTime spikes.

Implementation: p5.js canvas utilizing DOM sliders. The chart can be drawn using beginShape() and endShape() tracking a history array of frame rates.

Pausing the Performance

The Render Cycle Loop is an incredible engine for animation, but it is also a resource-hungry beast. Running a draw() loop 60 times a second drains laptop batteries and heats up smartphone processors.

If you are creating a static piece of generative art—perhaps a complex geometric mandala that never moves or animates—running the loop is a massive waste of energy. The computer is erasing and redrawing the exact same mandala 60 times a second.

You can halt the loop entirely by calling the No Loop Function (noLoop()). Typically placed at the bottom of setup(), noLoop() tells the engine to run the draw() function exactly once to paint the screen, and then stop forever. The sketch enters a frozen, static state.

Static Art Optimization

Palette tip Here's a secret to keeping your users happy: if your digital painting doesn't actually animate, always drop noLoop(); into your setup() function! It forcefully stops the engine from redrawing a static image 60 times a second, instantly saving battery life and keeping laptop fans silent.

But what if you want to generate a new static mandala every time the user clicks the mouse? You don't want continuous animation, but you do need to update the screen occasionally.

In this scenario, you can trigger a single, isolated execution of the draw() loop by calling the Redraw Function (redraw()). Calling redraw() inside a mouse click event will paint exactly one new frame and then immediately stop again.

Finally, if you have halted the loop with noLoop(), but an event occurs that requires continuous animation to resume (perhaps the user clicks a "Play" button), you can restart the infinite cycle by calling the Loop Control Function (loop()).

Adapting to the Environment: Responsiveness

A traditional oil painter controls the exact dimensions of their physical canvas, and they know the gallery wall it will hang on. A web-based creative coder has no such luxury. Your artwork might be viewed on a massive 4K ultra-wide monitor, a tiny cracked smartphone screen held in portrait orientation, or a tablet rotating wildly between the two.

A robust sketch must act like a liquid, adapting fluidly to its container.

The primary mechanism for this adaptation is the Window Resized Event (windowResized()). This is a special function that p5.js automatically listens for. The instant the user clicks and drags the corner of their web browser to change its size, or rotates their phone, the windowResized() function fires.

Inside this event function, we use the Resize Canvas Function (resizeCanvas(newWidth, newHeight)). The library provides two incredibly useful system variables: windowWidth and windowHeight. By passing these into resizeCanvas(), the digital drawing board instantly snaps to match the new dimensions of the browser window.

// This event runs whenever the user resizes the browser
function windowResized() {
  // Dynamically shrink or grow the canvas to match the new window size
  resizeCanvas(windowWidth, windowHeight);
}

Sometimes, you don't want your artwork constrained by the browser's menus, address bars, and tabs. You want total immersion. By calling the Full Screen Mode function (fullscreen(true)), you can command the browser to expand the sketch to take over the user's entire physical monitor. (Note: for security reasons, browsers require this command to be triggered by an explicit user action, like a mouse click or key press, to prevent websites from hijacking the screen unexpectedly).

The Density Problem: Retina Displays

Adapting to the size of the screen is only half the battle. Modern hardware has introduced a new complication: screen sharpness.

Older monitors had a 1-to-1 relationship: one pixel in your software code equaled one physical LED light on the hardware screen.

Then came "Retina" and high-density displays. To make text and images look incredibly crisp, hardware manufacturers packed four (or even nine) physical LED lights into the physical space that used to hold just one. This ratio between software pixels and hardware pixels is called the Display Density.

By default, p5.js automatically detects high-density displays and secretly scales up your canvas behind the scenes so that your graphics look razor-sharp.

However, this crispness comes at a massive computational cost. If you create an 800x600 canvas on a standard monitor, the computer calculates 480,000 pixels per frame. If you open that exact same sketch on a high-density Retina screen with a density of 2, the computer is secretly calculating a 1600x1200 canvas—nearly 2 million pixels per frame!

If you are writing an intensive 3D simulation with thousands of particles, the high-density display might cause your sketch to lag horribly. As the artist, you can intervene. You can use Pixel Density Control (pixelDensity(1)) inside your setup() function. This forces the canvas to ignore the high-density hardware and render at standard resolution. Your artwork might look slightly softer or "pixelated," but it will run blazing fast, prioritizing fluid motion over razor-sharp edges.

Modes of Execution and the Developer's Flashlight

As you learn p5.js throughout this book, we will write our code using Global Mode Execution.

In Global Mode, every tool in the p5.js library is universally available. You can type circle() or background() anywhere in your file, and the computer knows exactly what you mean. It is the easiest, most frictionless way to learn creative coding. It is like having a private art studio where all the paints are laid out on a central table, accessible from anywhere in the room.

However, the real world of professional web development is rarely so isolated. Eventually, you may want to embed your beautiful generative art piece into a complex, massive web application built with frameworks like React, Vue, or Angular.

If you use Global Mode in a massive web app, your private studio suddenly merges with everyone else's. Your variable named score might accidentally overwrite the website's shopping cart score variable. Chaos ensues.

To solve this, professional developers wrap their sketches in Instance Mode Execution. In Instance Mode, the entire sketch is bundled tightly inside a p5 Constructor Function (new p5()). This creates a protective bubble around your code. To draw a circle, you can no longer just say circle(); you must ask the specific instance of the library, often written as p.circle(). Instance Mode is like sharing a massive communal warehouse studio: you keep your paints strictly on your own desk so they don't spill onto your neighbor's work.

Finding the Truth with the Console

Regardless of whether you use Global or Instance mode, you will eventually write code that doesn't do what you expect. A shape will vanish off-screen, a color will render as black, or a loop will freeze the browser.

When a sketch is failing visually, beginners often try to guess the problem, randomly changing numbers and hoping the visual output fixes itself. This is a path to madness.

Professional coders rely on data. To peer inside the invisible brain of the program, we use Console Log Debugging.

Every web browser has a hidden "Developer Tools" panel featuring a text console. By writing console.log(myVariable) in your code, you command the computer to print the exact, real-time mathematical value of that variable to this hidden text console.

The Invisible Canvas

Palette thinking Think about it like this: the computer's memory is a vast, invisible canvas, and your screen is just a tiny window looking at one small piece of it. If your circle disappears, is it gone, or did a math error just push it miles off-screen? The console is your flashlight for looking into the dark, invisible spaces where the screen can't see!

Summary

We have mapped the foundational architecture of a creative coding environment. You now understand the top-left coordinate system, the execution pipeline from preload() to setup() to the continuous draw() render cycle, and the critical distinction between frame rates and real-time delta tracking. You also possess the tools to make your canvas responsive and debug logic errors using the console.

In the next chapter, we will take these structural concepts and begin rendering geometry to the screen using 2D primitive shapes.

Canvas Mastered!

Palette celebrating Incredible work, creators! You just mastered the top-left coordinate system and successfully wired up the preload(), setup(), and draw() lifecycle loops. Your digital studio is officially open for business!

See Annotated References