Infinite circle ripple with p5.js

Today we are going to make an infinitely looping, ripple effect similar to this.

ripple effect animation

I recommend you don't copy and paste the code but try typing it out, to help commit stuff to memory, but it will help with comprehension. I am aware this is a more accident prone approach but worth the effort.

The setup

first ripple with no fill

We will be using my favourite creative coding framework p5.js. I love using the online editor for the instant feedback. It also gives us the basic setup to get going instantly, it should look like this, with a grey background.

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(220);
}

Draw a circle in the middle of the canvas

circle in the middle of the screen

Let's start by drawing a static circle in the middle of the screen. The circle function from p5.js take three parameters, x position, y position and radius. While we are inside the draw function we have a few certain variables available to use, e.g. height and width, these give use the height and width of the canvas respectively. We can divide these values by two, to find the middle of the canvas. It may currently look like a static circle, but this draw function is being called 60 times per second, knowing this, we can leverage it to create animations.

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(220);
  circle(height / 2, width / 2, 100);
}

Rings as an object

first ripple with no fill

We will need to display lots of circles at once to generate this effect, we will use object oriented programming to do so. We will define a class called Ring it will accept three props, the x position, y position, and the circle radius. This class will be instantiated/used multiple times to make our multiple rings. The Ring class will contain one method/function called draw() which we will then call in the draw function. I would also like you to remove the white default fill of the circle by calling the noFill() function inside the draw function. The noFill() function is a function from p5.js which will remove the fill colour leaving only the outline.

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(220);

  noFill();

  const ring = new Ring(height / 2, width / 2, 100);
  ring.draw();
}

class Ring {
  constructor(x, y, radius) {
    this.x = x;
    this.y = y;
    this.radius = radius;
  }
  draw() {
    circle(this.x, this.y, this.radius);
  }
}

Multiple rings

To store the multiple circles we use an Array called rings. Similar to height and width, there is another variable available in the draw function called frameCount. This is a number which starts at 0 when the animation starts and increments by one each time the draw function is executed. We will divide the frameCount by 60, and then check the outcome with the remainder operator, therefore when there is no remainder we can assume a second has passed. We will loop the rings array, with a forEach() function, and call draw() on each ring in the array.

ripple effect animation
let rings = [];

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(220);

  noFill();

  // push a new circle into the array every second
  if (frameCount % 60 === 0) {
    // use the frame count to set the radius of the circle
    rings.push(new Ring(height / 2, width / 2, frameCount));
  }

  rings.forEach((ring) => {
    ring.draw();
  });
}

class Ring {
  constructor(x, y, radius) {
    this.x = x;
    this.y = y;
    this.radius = radius;
  }
  draw() {
    circle(this.x, this.y, this.radius);
  }
}

Growing the rings

We now have, rings being drawn at specific sizes each second, but we want the rings to gradually grow in size as time passes. Therefore we will create an update() function which will increase the radius by 1 each time the it is called.

ripple effect animation
let rings = [];

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(255);

  noFill();

  // Make it look pretty
  strokeWeight(8);

  if (frameCount % 60 === 0) {
    // radius now starts at zero
    rings.push(new Ring(height / 2, width / 2, 0));
  }

  rings.forEach((ring) => {
    // Call the update function each time we draw a circle
    ring.update();
    ring.draw();
  });
}

class Ring {
  constructor(x, y, radius) {
    this.x = x;
    this.y = y;
    this.radius = radius;
  }
  update() {
    // increase the radius of the circle by
    // one each time the update function is called
    this.radius += 1;
  }
  draw() {
    circle(this.x, this.y, this.radius);
  }
}

Improve performance

Our code from above has a little problem it will put a new ring in the array every second for as long as the browser is open. This is not performant as we will waste computing resources drawing rings off the screen. We can prevent this by removing a ring from the array when it has grown too large to be visible on the canvas with the shift() to remove the first item from the array.

ripple effect animation
let rings = [];

function setup() {
  createCanvas(400, 400);
}

function draw() {
  background(255);

  noFill();

  // Make it look pretty
  strokeWeight(8);

  if (frameCount % 60 === 0) {
    // radius now starts at zero
    rings.push(new Ring(height / 2, width / 2, 0));
  }

  // Limit the amount of rings in the array to nine
  if (rings.length > 9) {
    rings.shift();
  }

  rings.forEach((ring) => {
    // Call the update function each time we draw a circle
    ring.update();
    ring.draw();
  });
}

class Ring {
  constructor(x, y, radius) {
    this.x = x;
    this.y = y;
    this.radius = radius;
  }
  update() {
    // increase the radius of the circle by
    // one each time the update function is called
    this.radius += 1;
  }
  draw() {
    circle(this.x, this.y, this.radius);
  }
}

Well done for making it to the end, if you have any issues or comments feel free to get in touch.

Bonus points

  • Can you vary the colours of the lines?
  • Can you make the rings shrink in size?
  • Can you fill the circles with different colours?
  • Can you speed up or the slow down the animation?
  • Can you write this in a functional way without object orientation?
Back home