Page 10: Animation Loops: Timing

Spring 2026 Sample Solution

Animation Loop and Event Loop Review

The browser needs to respond to the user, so it runs an event loop: it keeps a queue of events, and processes each one in turn. For each event the browser can run piece of a program. Code runs in response to an event happening. This is important in the web browser, because we want the browser to do other things, rather than just waiting around for the user to do something. Therefore, most web programming is event-based.

If we tried to make a long-running loop, we would have to check for events. Instead, web programs do work in small pieces and return control to the browser event loop. If they have more work to do, they schedule an event.

RequestAnimationFrame requests an “event” for the next screen redraw - it’s basically saying “call this function in a few milliseconds, after the user has seen everything that has been drawn already”. The exact rate is not constant (it’s when the browser thinks it is time to repaint).

While requestAnimationFrame allows us to avoid having events that are “too fast” for redraw, it doesn’t provide control over how fast these events happen. If we want control over timing: to make things move at a specific rate (that is not tied to the computer) or if we want to adjust if the computer can’t keep up, we need to explicitly consider time.

Consider this trivial example (from the previous page):

1
2
3
4
5
6
function advanceRAF() {
  let v = (Number(slr.value)+1) % 120;
  slr.value = v;
  requestAnimationFrame(advanceRAF);
}
advanceRAF();

Problems:

  1. On my monitor with a 60fps refresh rate, it will take 2 seconds for the slider to go from end to end. On my laptop screen, it will take 1 second. I have no idea how long it will take on your computer.
  2. If the computer slows down (for example, the browser is processing so many events that it can’t keep up with the 120 hz update), it might take longer than 1 or 2 seconds to go through the loop. The slider will slow down as the computer slows down. The frame rate might not be even.

To fix this, we need to measure time between iterations of the loop. For the slider, we will move the slider forward based on how much time has elapsed. We’ll use real “clock time”, rather than the (unpredictable and unreliable) inter-frame time.

Keeping Time

So far, we’ve been using RequestAnimationFrame to create a “loop” - which goes as fast as it can. What if we want control over how fast things go?

Above, we said that RequestAnimationFrame generates an event some time in the future. The expected timing depends on the computer - not the program. The actual timing might be longer if the computer is slow, or busy doing other things). The key thing is we cannot count on RequestAnimationFrame calling us at a constant rate, or at a known rate. If we want things to move evenly, or we want things to take a certain amount of time, we need to be more careful.

The basic idea is that we look at the actual time (by checking the clock) to see how much time has elapsed. In old code, we would check the actual clock time using performance.now() - which returns the clock time. You may see this in old workbook code. Since almost everything that uses RequestAnimationFrame needed to access the time, it now passes a time value as an argument to the function that gets called.

Technically, all of the examples so far have a type error, because the function passed to RequestAnimationFrame should take an argument. So rather than something like this:

1
window.requestAnimationFrame( function() { /* do something */} );
it should be
1
window.requestAnimationFrame( function(time) { /* do something */} );
Aside: Timing in requestAnimationFrame

The prefered way to use the time is to use the parameter passed to the requestAnimationFrame callback. The alternative is to explicitly check what time it is. There are (at least) two different functions you can use to get the time with millisecond accuracy: Date.now and performance.now.

There are differences: performance.now gives a floating point number, which might give more resolution than milliseconds; Date.now is tied to the system clock, so might change if the system clock is updated. Date.now is tied to the actual time, whereas performance.now is relative to when the page was loaded.

In the original versions of the workbooks, we preferred performance.now beacause it made the use of “clock time” explicit. There are subtle technical advantages to using the parameter to requestAnimationFrame and it is simpler, so we prefer that now.

There are many ways to make use of the time stamp. The most common pattern is to remember the previous time stamp and see how much time has elapsed, and “advance” things by an appropriate amount.

Here is a simple example. We would like to have the speed of the motion of the slider be such that it takes 1 second for the slider to cover its distance. This is implemented in three different ways:

  1. We assume that each iteration take 16ms and hope for the best. If the computer slows down, or gets busy or runs at a different rate, you get a different result.
  2. We look at how long it has been since the last step, and move the slider accordingly.
  3. We look at the total time the program has been running, and base the position on that (so it wraps around every second)

There is actually another catch: because the slider actually moves in steps, we end up with rounding errors. The default sliders have 100 steps. If we assume 1/60th of a second, this means each step should move the slider 1/60th of the overall distance, or 1.6 - which gets rounded up to 2. If you have a 60hz browser (like my laptop) things appear to go too fast. If you have a 30hz computer (3.3 gets rounded down to 3), things go too slow.

So, even though all of these sliders should move the length of the slider in 1 second, they will all appear different. In fact, the top 2 will look different on different computers!

01-10-01   view Kinds of boxes:
view - look at the box and experiment with it
examine - look at the code for the box
edit - change the box's content
rubric - several steps are suggested in the rubric page - form elements on page in rubric
   01-10-01.html

So, what do you do? You must be careful with time. Do not assume the frame rate is constant (do not use strategy #1). Keep track of time, and move things accordingly. There is a reason why the “compute deltas” strategy (strategy #2, sliders 2,5,6) is advantageous. We’ll see it in the next box.

But make sure you understand the code in 01-10-01.html. You will write lots of code like this over the course of the semester.

Why time deltas?

Here is an example of why you might want to use the “time delta” strategy rather than just using a “global time.” Here we mix box 3 and 4 to add a stop button. Notice what happens when you turn things off and back on - because the global time kept going when things were stopped, the slider “jumps” to a new position!

01-10-02   rubric Kinds of boxes:
view - look at the box and experiment with it
examine - look at the code for the box
edit - change the box's content
rubric - several steps are suggested in the rubric page - form elements on page in rubric
   01-10-02.html
Kind Kind of rubric items:
standard - expected from all students
advanced - used to get a better grade
creative - an opportunity to do something creative
levels - rubric item graded by selected achievement level
optional - not required, but encouraged
Description
standard change slider speed to half

Of course, you could do something to avoid the jumps even with the global clock (for example, by appropriately resetting the start time).

Again, make sure you understand this code (in 01-10-02.html). Later in the workbook, we will use strategies like this to make graphics move.

Also, notice that the loops run even if the sliders aren’t being updated. In the real world, this may be a bad idea, since it is wasteful.

To make sure you’re still paying attention, and you understand what’s going on, modify the JavaScript code in 01-10-02.html so that both the sliders move at half their current speed. Hint: you should only have to edit lines 27 and 51.

Intentionally Annoying…

This example makes text blink, because sliders were getting boring.

From a technique perspective, the important point is the idea of controlling for timing (as discussed above). Because window.requestAnimationFrame doesn’t provide a constant rate, we need to check the actual “clock” time to see if enough time has passed.

01-10-03   view Kinds of boxes:
view - look at the box and experiment with it
examine - look at the code for the box
edit - change the box's content
rubric - several steps are suggested in the rubric page - form elements on page in rubric
   01-10-03.html   01-10-03.js

The box 01-10-03.js is an important example to read. It makes use of closures and other functional programming tricks. And it uses the “clock time” to control speed, independently of how fast window.requestAnimationFrame triggers events. These are the kinds of techniques you will use a lot when we do graphics programming. But, you won’t have to wait that long. You’ll get some things to try on the Next Page.

How does this connect to buffering?

On Page  8  (Displays and Frame Rate) we talked about the concept of buffering. Now that we’ve seen how the web browser handles timing, we can connect the programs we write to the concepts.

The actual mechanics of the browser use a more complicated buffering mechanism (there is a third buffer). But here is an explanation that is good enough…

  1. When we make a change to the web page, it happens in the back buffer. In fact, some of those changes only happen when the event loop has control and can turn changes to the document (DOM) to changes in the image that is shown.
  2. The event loop takes care of the “swap” (making the back buffer visible). Technically, it doesn’t swap: it “composites” the back buffer onto the front buffer. This allows it to “add” images together (rather than erasing everything every time). Important point: if the event loop doesn’t have control, it cannot make the contents of the back buffer visible.
  3. requestAnimationFrame schedules an event for after the next “swap” event happens.

Onward…

Enough about how we make things move. Let’s move on to letting you try it yourself…

Next: Page  11 - Try it Yourself