Page 5: Animation Loops

CS559 Spring 2022 Sample Solution

Thus far, we can do things in response to the user. Our 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.

But what happens if we want things to happen without the user doing anything? For example, when we want to animate something (have it move on its own).

We might make a loop - having things move each step, over and over until its done. This is bad because we have to handle events in the loop or ignore the user.

Instead, the web paradigm is to use events - but have events that are not tied to user actions. Our program requests an event some time in the future. It’s kindof like a timer - the event is when the time goes off. Our program gives up control - we schedule the event and return to the browser so it can handle other events. Once the “timer” event goes off, it gets put in the queue to be processed.

RequestAnimationFrame requests an “event” for the next screen redraw - it’s basically saying “call me in approximately 16ms”. If something happens every 16 milliseconds, that means it happens 60 times per second, which is a common target rate. The exact rate is not constant (it’s when the browser things it’s time to repaint). See the Official RequestAnimationFrame Docs if you want more information.

Understanding this model of web programming is important - we’ll discuss it in lecture. In web programming, we write our code to do work in small chunks, and when it does, it “returns” to the browser main loop. That way the “browser main loop” can make sure that all of the different things can run. Historically, web browsers didn’t actually do things in parallel.

Box 1 - A Toy Example

This box has a simple example of RequestAnimationFrame.

If you read the code (and you should, you can figure out that this is page 5 box 1, so the file is 01-05-01.js) you’ll see that when the button is clicked, the first line is changed and a timer is set to call the function future1 in the future. When future1 is called, it changes line 2, and schedules an event to call future2 in the future. And this repeats a 3rd time.

A more realistic example

Here is some actual animation (using a slider, since that’s all we know).

The pattern that we see here in 01-05-02.html is a common one: we create an infinite loop by having a function schedule itself to be called in the future. Notice how we also need to call the function the first time. And read the comments, since there are some tips about dealing with a JavaScript weirdness.

You may wonder why we don’t do a more traditional loop, such as…

1
2
3
4
while(true) {
    let newValue = (Number(slr1.value)+1) % 100;
    slr1.value = newValue.toString();   
}

Don’t try this! (for me it crashed the browser) While it makes an infinite loop, it makes an infinite loop that blocks everything else. It never returns control to the browser (to allow other events to happen). In fact, you’ll never even see the slider move, since the browser is so busy running this infinite loop that it never gets around to redrawing things (redrawing actually happens as part of event processing).

Box 2B: Parallelism

Really understanding that example is so critical, that I am going to have another example: lets make multiple sliders all go at once.

Of course, we could put the sliders all in the loop from the previous box. But, let’s pretend each slider was some more complicated thing, so we wanted to keep the code separate and independent. In the idea world, we could have the code for each slider run in parallel.

Make sure you are understanding what is happening in 01-05-02b.html. Including the way that I used separate modules (even though they are in the same file), so I could re-use the function names.

This “psuedo-parallelism” is really a key aspect of browser based programming: we write our code to do work in small chunks, and when it does, it “returns” to the browser main loop. That way the “browser main loop” can make sure that all of the different things can run in “parallel” - even though the really are not really running in parallel, they are taking turns. (Geeky point: at least in the old days, each web page was really a single thread. Modern web browsers might actually parallelize things in some cases.)

Box 2C: Programming Practice

This is a good opportunity to discuss some programming concepts. Event programming means we write little functions to do things (since each little function does a little bit).

Here is a re-write of the previous example. Except that rather than write 3 functions, we’ll write a loop that creates the three functions we need. This example has two sets of sliders: one that is written correctly (the top one), and a second version of the code that has a bug in it (the lower set of three sliders).

Read the code for 01-05-02c.html.

This code uses closures which are an important programming concept. They are, admittedly, tricky. If you have never seen closures before, it will take some practice before you understand them. However, once you do understand them, they are extremely useful. Try to understand how the loop for the top sliders works. Try to understand why the second loop does not - even though they are very similar.

To help you learn about closures, there is a Closure Tutorial Video, and an optional Functional Programming Tutorial (repo) which is a small workbook that covers the important concepts..

There are a lot of comments in the code for 01-05-02c.html. Take the time to try to understand this code.

While programming techniques, like closures, are not necessarily “graphics” - learning them is part of the class. You can expect exam questions about closures.

Box 3 Even more infinite loop examples

01-05-03.html has some more complex versions of that infinite loop from the previous box. Be sure to look at the code to understand how it works! These kinds of things (looking at input elements within animation loops) will happen all of the time when we start doing graphics programming.

Box 4: 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 “approximately” 16 milliseconds in the future. It might be longer than that (if the computer is slow, or busy doing other things). In some cases, it might be faster than that. 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.

It turns out to be even worse: on some computers with 30hz refresh (one of mine!) some browsers only generate RequestAnimationFrame 30 times a second (approximately 33ms delays).

So, on different computers, I get very different behaviors! Which is not at all desirable.

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 timer 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 */} );

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!

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-05-04.html. You will write lots of code like this over the course of the semester.

Box 4b: 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!

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-05-04b.html). Next week, we’ll be using 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 its wasteful.

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

Box 01-05-04b Rubric (3 points total)
Points (3):
Box 01-05-04b
3 pt
change slider speed to half

Box 5: 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 in Box 4 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-05-05.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.

Next: Try it Yourself

Page 5 Rubric (3 points total)
Points (3):
Box 01-05-04b
3 pt
change slider speed to half