mystreie
mystreie

Reputation: 153

requestAnimationFrame confused

I am starting to learn with this method requestAnimationFrame, but I found it is little bit hard to understand (well at least for me). I know the how the setInterval/setTimeout works but when I try typing some simple code to test as:

function message(){console.log('hello');}
requestAnimationFrame(message);

This does not make any animation in the console which the hello message appear only once, but when I call requestAnmiationFrame(message) inside the message function the looping is infinity (so it is working).

But in another way I can use setInterval/setTimeout to call this message function everywhere instead of has to been inside the message function.

So may I understand that the requestAnimationFrame() method has to be run inside the function? Or should I say there is a function has a default for loop for looping itself infinity times when using a requestAnimiationFrame() method inside?

The question might be stupid but really hope someone who can enlighten me.

Upvotes: 3

Views: 1180

Answers (2)

Sagiv b.g
Sagiv b.g

Reputation: 31024

There is a huge difference between rAF(Request Animation Frame) and timers (setTimeout / setInterval).

Both are creating tasks (your callbacks) that runs async, that is the browser queuing them and invoke them at certain points in time.
The big difference is when they get execute and priority.

We need to understand who is managing the Que and under what conditions the callbacks are allowed to run.

I will try to keep it simple.

Synchronous JavaScript

JavaScript is "single threaded", that means you can't run async operations in plain JavaScript.
The browser API provides you some methods to run code async:

  • timers
  • rAF
  • DOM events
  • promises (this is a different kind of task)
  • xhr
  • etc...

When you use one of these methods, you actually pass a function reference to browser to invoke it at (hopefully) the desired point of time.

The event loop

The browser will do it's thing based on the API you used, and will Que your callback in the task Que (except promises, these are micro tasks).

The big question is when will it pass it back to "javascript-land" into the call stack?
This is the job of the event loop, it will check for 2 conditions:

  1. Is the callstack is empty (meaning no functions are currently running on the "main thread").
  2. Is there any code left to run on the "main thread".

If it meets these 2 conditions, it will pull the next task from the Que and push it to the call stack.

Now let's take setTimeout as an example.
Given this code:

setTimeout(() => console.log('timeout'), 0);
console.log('start');
console.log('end');

setTimout should wait 0ms then log timeout, so we should think that the log order will be:

  1. timeout
  2. start
  3. end

But actually it is:

  1. start
  2. end
  3. timeout

the reason is because we passed the callback to the browser, which waited 0ms then pushed it to the task que.
Now its the event loop's job to check the 2 conditions mentioned above:

  1. Is the callstack is empty (meaning no functions are currently running on the "main thread").
  2. Is there any code left to run on the "main thread".

The answer for both is no because we still has the 2 console.log lines to run on the main execution context.
So it waits, when we finish the log it then passes the callback from the que to the call stack and then we log timeout.

Most of the async callbacks are working this way.
note that promises are different, they are micro-tasks and have their own Que with higher priority then regular callback tasks.

OK, all this talk and i didn't answer your question yet.

But one more thing before the answer.

requestanimationFrame

Is another browser API that lets you run stuff just before the browser is about to run the render steps:

  1. style calculation
  2. layout
  3. the actual printing

So for example, if you want to update the left position of an element on the next frame, this is where you would do it.

And this is a possible implementation:

requestAnimationFrame(moveBox)

If you want it to keep moving on each frame you should call it recursively:

function callback() {
    moveBox();
    requestAnimationFrame(callback);
}
callback(); 

So why or when to use rAF over timers?

setInterval(moveBox, 0)

This will run faster than the rAF implementation. The reason it will run faster is because the callback will get invoked several times before the browser will get a chance to re-paint (processing the render steps), so you will "update" the left position several times but the browser will only re-paint the last value you passed.
For example it will jump from 0px to 15px instead of increment it one pixel at a time.

Basically what you could do is "guess" how much time will pass between frames. The most common approach is to calculate "60 frame per second" as most screens are supporting this ratio.

setInterval(moveBox2, 1000 / 60)  

But even this would not be smooth or consistent as it's not really 60 fps, on top of that maybe the current device and screen doesn't refresh at the same rate you thought.

So the best API for dealing with DOM animations, batching updates and measuring stuff is requestAnimationFrame.

Running Example

I set up a small example of moving a box with rAF and the 2 solutions of setInterval, hope it helps.

const showBox2 = location.search.includes('box2');
const showBox3 = location.search.includes('box3');
const box1 = document.getElementById('box1');
const box2 = document.getElementById('box2');
const box3 = document.getElementById('box3');

// move by 1 px
function move1px(el) {
  let left = 0;
  return function() {
    // infinite looping logic - if off screen start over
    if (document.documentElement.clientWidth < left) {
      left = 0;
    } else {
      left++;
    }
    // move left by 1 px
    el.style.left = left + 'px';
  }
}
// raf solution
const moveBox1 = move1px(box1);

function callback() {
  moveBox1();
  requestAnimationFrame(callback);
}
callback();

// setInterval solution
const moveBox2 = move1px(box2);
setInterval(moveBox2, 0);

// setInterval hack
const moveBox3 = move1px(box3);
setInterval(moveBox3, 1000 / 60);
body {
  overflow: hidden;
}

#root {
  display: flex;
  flex-direction: column;
}

.box {
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;
  height: 30px;
  width: 150px;
  border: 1px solid #333;
  margin: 10px 0;
  color: #fff;
  font-size: 1em;
}

#box1 {
  background-color: slateblue;
}

#box2 {
  background-color: brown;
}

#box3 {
  background-color: green;
}
<div id="root">
  <div class="box" id="box3">setInterval hack</div>
  <div class="box" id="box1">rAF</div>
  <div class="box" id="box2">setInterval</div>
</div>

Upvotes: 2

user2697924
user2697924

Reputation:

requestAnimationFrame doesn't actually render anything for you, it simply syncs the browser's rendering loop to your code, so you can use it to programmatically render animations on screen.

What it does is fires the callback function that you provide it every time the browser renders a frame. Here's a good example of what it's used for: making a javascript game

Upvotes: 1

Related Questions