Intro to Object-Oriented Programming
Use JavaScript classes to represent self-contained entities — the basis of every complex sketch.
The problem classes solve
Imagine 50 balls bouncing around the screen. Without classes, you need 50 separate variables for x, 50 for y, 50 for velocity… Managing that is impossible.
Arrays of plain objects help (we covered that in article 4), but they have a limitation: the behaviour (the update and draw code) lives separately from the data. As sketches grow, this separation becomes messy.
A class bundles data and behaviour into one reusable unit.
Defining a class
class Ball {
constructor(x, y) {
this.x = x;
this.y = y;
this.vx = random(-3, 3);
this.vy = random(-3, 3);
this.r = random(8, 20);
this.hue = random(360);
}
update() {
this.x += this.vx;
this.y += this.vy;
if (this.x < this.r || this.x > width - this.r) this.vx *= -1;
if (this.y < this.r || this.y > height - this.r) this.vy *= -1;
}
draw() {
colorMode(HSB, 360, 100, 100);
fill(this.hue, 70, 90);
noStroke();
circle(this.x, this.y, this.r * 2);
}
}
The constructor runs when you create a new instance. this refers to the specific instance being created or used.
Creating instances
let balls = [];
function setup() {
createCanvas(700, 500);
for (let i = 0; i < 40; i++) {
balls.push(new Ball(random(width), random(height)));
}
}
function draw() {
background(20);
for (let b of balls) {
b.update();
b.draw();
}
}
new Ball(x, y) creates an instance. Each instance has its own independent copy of all properties.
Adding more methods
Methods can be anything — reactions to input, state checks, interactions with other objects:
class Ball {
constructor(x, y) {
this.x = x;
this.y = y;
this.vx = random(-2, 2);
this.vy = random(-2, 2);
this.r = random(10, 25);
this.hue = random(360);
this.alive = true;
}
update() {
this.x += this.vx;
this.y += this.vy;
if (this.x < this.r || this.x > width - this.r) this.vx *= -1;
if (this.y < this.r || this.y > height - this.r) this.vy *= -1;
}
isHovered() {
return dist(mouseX, mouseY, this.x, this.y) < this.r;
}
onClick() {
this.hue = (this.hue + 60) % 360;
}
draw() {
colorMode(HSB, 360, 100, 100);
let sat = this.isHovered() ? 100 : 70;
fill(this.hue, sat, 90);
stroke(this.hue, 50, 100, 50);
strokeWeight(2);
circle(this.x, this.y, this.r * 2);
}
}
function mouseClicked() {
for (let b of balls) {
if (b.isHovered()) b.onClick();
}
}
Inheritance — extending a class
One class can extend another, inheriting all its properties and methods:
class Particle extends Ball {
constructor(x, y) {
super(x, y); // call the parent constructor
this.life = 255;
this.fade = random(2, 6);
}
update() {
super.update(); // run the parent update
this.life -= this.fade;
}
isDead() {
return this.life <= 0;
}
draw() {
colorMode(HSB, 360, 100, 100, 255);
fill(this.hue, 70, 90, this.life);
noStroke();
circle(this.x, this.y, this.r * 2);
}
}
Use super(args) to call the parent constructor, and super.method() to call a parent method.
A complete OOP sketch
class Bubble {
constructor() {
this.reset();
}
reset() {
this.x = random(width);
this.y = height + random(50);
this.r = random(6, 24);
this.vy = random(-0.5, -1.8);
this.vx = random(-0.3, 0.3);
this.hue = random(180, 260);
this.life = 255;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.life -= 1.5;
}
isDead() {
return this.life <= 0 || this.y < -this.r;
}
draw() {
colorMode(HSB, 360, 100, 100, 255);
noFill();
stroke(this.hue, 60, 90, this.life);
strokeWeight(1.5);
circle(this.x, this.y, this.r * 2);
// Highlight
stroke(this.hue, 20, 100, this.life * 0.6);
strokeWeight(1);
arc(this.x - this.r * 0.3, this.y - this.r * 0.3,
this.r * 0.8, this.r * 0.8, PI + 0.3, PI + 1.2);
}
}
let bubbles = [];
function setup() {
createCanvas(600, 700);
for (let i = 0; i < 60; i++) {
let b = new Bubble();
b.y = random(height); // start scattered, not just at the bottom
bubbles.push(b);
}
}
function draw() {
background(10, 30, 60);
for (let b of bubbles) {
b.update();
b.draw();
if (b.isDead()) b.reset();
}
}
When to use classes
Use a class when:
- You need multiple instances of the same type of thing
- Each instance has its own independent state
- The update/draw logic is non-trivial
Don’t use a class just for grouping functions — a plain object or a module works better for that.
Key takeaways
- A class bundles data (
this.x,this.y) and behaviour (update(),draw()) into one unit constructor()runs whennew ClassName()is called; initialise all properties there- Each
newcall creates an independent instance with its own copy of all properties - Methods can check state, react to input, and interact with other objects
extendscreates a subclass that inherits all parent properties and methods;super()calls the parent constructor or method- Arrays of class instances are the backbone of every complex p5.js sketch