The alternative to noLoop(). This tutorial shows how to pause and resume individual parts of a sketch by controlling animation time, rather than stopping the entire draw loop.
Use the ◀ ▶ buttons or arrow keys to step through the stages. Click inside the canvas to pause/resume, and press keys where noted. Drag any number in the code to adjust it live.
This sketch animates two objects: a circle that moves from left to right, and a square that spins in place.
Click the mouse button to pause them both. Click again to resume them.
This uses noLoop() to pause and loop() to resume. This is simple, but it doesn’t give you much control over what is paused and what is resumed.
function setup() { createCanvas(windowWidth, 250);}function draw() { background(100, 50); // veering circle let x = (millis() / 5) % width; let y = map(sin(millis() / 700), -1, 1, 25, height - 25); circle(x, y, 50); // spinning square rectMode(CENTER); rotateAbout(millis() / 800, 200, height / 2); rect(200, height / 2, 100, 100);}function mousePressed() { if (isLooping()) { noLoop(); } else { loop(); }}function rotateAbout(angle, x, y) { translate(x, y); rotate(angle); translate(-x, -y);}
First, some setup. We are going to need to distinguish between the real time (returned by millis()), and the animation time.
Introduce a variable that captures the value of millis() each frame, and use its value instead of calling millis().
let animationTime;function setup() { createCanvas(windowWidth, 250);}function draw() { background(100, 50); animationTime = millis(); // veering circle let x = (animationTime / 5) % width; let y = map(sin(animationTime / 700), -1, 1, 25, height - 25); circle(x, y, 50); // spinning square rectMode(CENTER); rotateAbout(animationTime / 800, 200, height / 2); rect(200, height / 2, 100, 100);}function mousePressed() { if (isLooping()) { noLoop(); } else { loop(); }}function rotateAbout(angle, x, y) { translate(x, y); rotate(angle); translate(-x, -y);}
Introduce another variable, to tell whether to update animationTime from millis(). Modify mousePressed to change the value of this variable, instead of calling loop() and noLoop().
let animationTime;let isAnimated = true;function setup() { createCanvas(windowWidth, 250);}function draw() { background(100, 50); if (isAnimated) { animationTime = millis(); } // veering circle let x = (animationTime / 5) % width; let y = map(sin(animationTime / 700), -1, 1, 25, height - 25); circle(x, y, 50); // spinning square rectMode(CENTER); rotateAbout(animationTime / 800, 200, height / 2); rect(200, height / 2, 100, 100);}function mousePressed() { if (isAnimated) { isAnimated = false; } else { isAnimated = true; }}function rotateAbout(angle, x, y) { translate(x, y); rotate(angle); translate(-x, -y);}
Instead of an if statement, mousePressed() could also use the not operator (!), that maps false onto true and true onto false.
let animationTime;let isAnimated = true;function setup() { createCanvas(windowWidth, 250);}function draw() { background(100, 50); if (isAnimated) { animationTime = millis(); } // veering circle let x = (animationTime / 5) % width; let y = map(sin(animationTime / 700), -1, 1, 25, height - 25); circle(x, y, 50); // spinning square rectMode(CENTER); rotateAbout(animationTime / 800, 200, height / 2); rect(200, height / 2, 100, 100);}function mousePressed() { isAnimated = !isAnimated;}function rotateAbout(angle, x, y) { translate(x, y); rotate(angle); translate(-x, -y);}
This gives us more flexibility. For example, we could keep the square spinning (by driving it from millis()), while pausing and resuming the veering circle.
let animationTime;let isAnimated = true;function setup() { createCanvas(windowWidth, 250);}function draw() { background(100, 50); if (isAnimated) { animationTime = millis(); } // veering circle let x = (animationTime / 5) % width; let y = map(sin(animationTime / 700), -1, 1, 25, height - 25); circle(x, y, 50); // spinning square rectMode(CENTER); rotateAbout(millis() / 800, 200, height / 2); rect(200, height / 2, 100, 100);}function mousePressed() { isAnimated = !isAnimated;}function rotateAbout(angle, x, y) { translate(x, y); rotate(angle); translate(-x, -y);}
Or, we can have separate control over different parts of the sketch.
Here, mousePressed toggles the animation state of the veering circle; keyPressed toggles the animation state of the spinning square.
Let’s return to the simpler version of the code, that controls the animation of everything.
You may notice that when you press the mouse to pause the animation, and then resume it later, the circle and square jump ahead.
(This was an issue with the original version of the sketch, that used loop() and noLoop(), as well.)
This is because the clock that millis() reads is still running, even when isAnimated is false. If we pause for five seconds and then set isAnimated back to true, the value of animationTime will jump ahead by 5000.
let animationTime;let isAnimated = true;function setup() { createCanvas(windowWidth, 250);}function draw() { background(100, 50); if (isAnimated) { animationTime = millis(); } // veering circle let x = (animationTime / 5) % width; let y = map(sin(animationTime / 700), -1, 1, 25, height - 25); circle(x, y, 50); // spinning square rectMode(CENTER); rotateAbout(animationTime / 800, 200, height / 2); rect(200, height / 2, 100, 100);}function mousePressed() { isAnimated = !isAnimated;}function rotateAbout(angle, x, y) { translate(x, y); rotate(angle); translate(-x, -y);}
Here’s a pattern that fixes this. It uses an additional variable to remember when the animation was paused, so that it can offset millis() by the amount of time that the animation stayed paused, in creating the synthetic time value for animationTime.
Now that we have control over time, we can also change it in other ways. For example, pressing the up and down arrows speeds up and slows down the animation, by changing the relationship between the actual time (millis()) and the time that the veering circle and spinning square code “see”.
Note: In OpenProcessing.org, you will need to click on the canvas before the sketch is notified of your key strokes. (Before you click, the canvas does not have “focus.”) The first click will pause the animation, so you will actually need to click twice. Then you can use the up and down arrows to speed up or slow down the animation.