Mastering Time: Creating Stop-Motion Effects in p5.js with setTimeout()

Want to add a unique, rhythmic "stop-motion" feel to your p5.js sketches? Today, I’ll show you a simple yet powerful technique to control the flow of time in your animations.

The Power of setTimeout()

The secret to this effect lies in a standard JavaScript function: setTimeout().


[Japanese version / 日本語版はこちら]


What is setTimeout()?

Think of setTimeout() as a digital kitchen timer. You tell it to wait for a specific amount of time, and once the timer goes off, it executes a function. Keep in mind that this isn't a p5.js-specific tool; it’s a core feature of JavaScript itself.

The syntax is straightforward:

setTimeout(func, delay);

Basically, you're saying: "Wait 'delay' milliseconds, then run 'func' function."

 

Understanding Asynchronous Magic

Imagine you set a timer for 100ms. Does your entire program freeze and wait for the countdown? Not at all.

Standard code usually runs line-by-line. For example, this snippet calculates positions and draws a circle instantly:


  let x = radius * cos(t);
  let y = radius * sin(t);
  circle(x, y, 10);

But when you introduce an asynchronous function like setTimeout(), it starts a "side mission":


  setTimeout(funcA, delay);
  let x = radius * cos(t);
  let y = radius * sin(t);
  circle(x, y, 10);

The timer ticks independently of your drawing logic. Once it hits zero, funcA is triggered. Below is a fun experiment: an animation that runs entirely without the p5.js draw() loop. Can you guess what it will look like before you hit play?


function setup() {
  createCanvas(640, 640);
  noLoop();

  setTimeout(drawCircle, 3000, 64);
  setTimeout(brightBg, 2000);
  setTimeout(drawCircle, 1000, 255);

  background(0);
  rect(0, 0, 200, 200);
}

function brightBg() {
  background(240, 128);
}

function drawCircle(c) {
  fill(c);
  circle(width * 0.5, height * 0.5, 200);
}


 

Asynchronous functions

The setTimeout() is an asynchronous function. Asynchronous functions allow your code to handle multiple tasks without blocking the main program.

The asynchronous function is not only setTimeout(). The loadImage() of p5.js is one of the asynchronous functions too.

You might have seen the error 'There is no such object!' when you used the loadImage() in the setup() or the draw(). That's because the code executes the setup() and draw() while the loadImage() slowly loads the image file. So you should use loadImage() in the preload().

Please read this for details about setTimeout().

setTimeout() | MDN

 

Bringing Stop-Motion to p5.js

Now, let's apply this "background timer" concept to create a rhythmic stop-motion effect.

 

The Traditional p5.js Animation

Take a look at these rotating circles. Usually, they move in a smooth, continuous loop. But what if we want them to "freeze" momentarily whenever they align into perfect rows?

 

The Formula: setTimeout() + noLoop()

The logic is simple: tell p5.js to stop drawing, then set a timer to wake it back up.


setTimeout(loop, 1000); // In 1 second, start the loop again
noLoop();               // Stop the loop right now!

This creates a perfect 1000-millisecond pause.

 

Final Result & p5.js Code

By combining setTimeout(), noLoop(), and loop(), we can transform a fluid motion into a calculated, cinematic experience. Feel free to use this CC0-licensed code in your own projects!



/**
 * p5.js stop-motion with setTimeout().
 *
 * @author @deconbatch
 * @version 0.1
 * @license CC0 https://creativecommons.org/publicdomain/zero/1.0/
 * p5.js 1.1.3
 * created 2022.05.29
 */

const w = 740;
const h = w;
const arms = 2;     // arm number
const nodes = 32;   // node number on arm
const armLen = 0.4; // arm length
const speed = 0.02;

let stopNum;
let timer;

function setup() {
  createCanvas(w, h);
  frameRate(30);
  noFill();
  stroke(32);
  strokeWeight(2);

  stopNum = 1;
}

function draw() {

  const time = frameCount * speed;
  
  background(224);
  translate(w * 0.5, h * 0.5);
  for (let node = 0; node < nodes; node++) {
    let nRatio = map(node, 0, nodes, 0.0, 1.0);
    let nSize = nRatio * w * 0.1;
    let radius = w * armLen * nRatio;
    for (let arm = 0; arm < arms; arm++) {
      let theta = TWO_PI * (arm + nRatio) / arms * time;
      let x = radius * cos(theta);
      let y = radius * sin(theta);
      circle(x, y, nSize);
    }
  }

  // stop-motion
  if (time >= stopNum) {
    clearTimeout(timer);
    timer = setTimeout(reLoop, 1000);
    noLoop();
  }

}

function reLoop() {
  stopNum++;
  loop();
}

 

Pro Tip: Don't Forget to Clean Up!

When using setTimeout() inside the draw() loop, there's a small but crucial detail to keep in mind: preventing timer overlap.

If your condition (like time >= stopNum) remains true for more than one frame, p5.js might trigger setTimeout() multiple times in a split second. This can lead to a "memory leak" or cause your animation to stutter as multiple timers compete to run the same function.

To keep your sketch running smoothly, always use clearTimeout() before starting a new timer:


if (time >= stopNum) {
  clearTimeout(timer); // Cancel any existing timer first!
  timer = setTimeout(reLoop, 1000);
  noLoop();
}


This simple line ensures that only one timer is active at any given time, keeping your browser healthy and your animation precise.

 

Happy Coding!

I hope this sparks some new ideas for your creative sketches. I can't wait to see how you manipulate time in your next piece!

 

Next Post Previous Post
No Comment
Add Comment
comment url