The timer

SetInterval () – The specified number of milliseconds to execute the specified code continuously (all the time).

SetTimeout () – Executes the specified code after the specified number of milliseconds (only once).

RequestAnimationFrame () – How often the screen refreshes (only once).

Use the setInterVal:

    function doStuff(){
        // this is code that needs to be executed for a period of time
    }
    setInterVal(doStuff, 100);
Copy the code

Use setTimeout to simulate setInterval:

    function tick() {
        doStuff();
        setTimeout(tick, 100);
    }
    tick();
Copy the code

Take a look at the difference between the two under normal conditions:

SetInterval performs doStuff every 100ms, and setTimeout performs doStuff every 100ms. Therefore, the interval between each timer is 100 + T(doStuff execution time is T). This T is the key to this article.

  • If T can be ignored, the effect is basically the same.
  • When T <= 100, the interval for setInterval is 100 and the interval for setTimeout is 100+T.
  • If T > 100, setTimeout remains as shown above, with an interval of 100+T between two timers. What about setInterval?

Take a look at the picture below:

At 0ms, timer 1 starts to enter the macro task queue. At 100ms, timer 1 starts to execute doStuff1, the queue is empty, and timer 2 enters the queue. At 200ms, timer 3 was skipped because timer 2(doStuff1) was in the queue. Browsers do not create two identical interval timers at the same time. At 300ms, timer 2 starts running, the queue is empty, and timer 4 enters the queue. And so on ~

So let’s verify that with code. T Set the parameter to 140ms. We let the timer run 5 times, according to the above understanding, the total running time should be: 100+5*140 = 800ms. The code is as follows:

let i = 0;
console.time("Total time");

function doStuff() {
    console.log("delay");
    dead(140);
    console.timeEnd("Test");
}

function dead(delay) {
    var start = new Date().getTime();
    while (new Date().getTime() < start + delay);
}

let timer = setInterval(() = > {
    i++;
    if (i > 4) {
        clearInterval(timer);
        setTimeout(() = > {
        console.timeEnd("Total time");
        }, 0);
    }
    console.log("interval start");
    console.time("Test");
    doStuff();
}, 100);
consle
Copy the code

It can be seen that the timer ran for 5 times, and the total time was indeed 100 + 140*5 = 800ms.

If we set dead(250), the total test time is 100 + 250 * 5 = 1350;

What if the code in doStuff is asynchronous? For example, we often use promises. The result is returned at 140ms. The code is as follows:

    let i = 0;
    console.time("Total time");

    function delay(i) {
        promise(i);
    }

    function promise(i) {
        return new Promise((resole,reject) = > {
            setTimeout(() = > {
                resole(i);
            }, 140);
        }).then(res= > {
            console.log("res", res); })}let timer = setInterval(() = > {
        i++;
        if (i > 4) {
            clearInterval(timer);
            setTimeout(() = > {
                console.timeEnd("Total time");
            }, 0);
        }
        delay(i);
    }, 100);
Copy the code

You can see that the total time is 500ms, and the asynchronous code requesting the interface does not block the timer. This is also easy to understand. The synchronous code in the timer is directly queued, while the asynchronous code registers the event and is queued when it is done. So when the asynchronous code registers the event, the timer is finished, not finished until the asynchronous code returns, otherwise the total time of the timer for 5 times would be 800ms.

According to the above code effect summary

SetInterval simply queues code at a certain point in time, and skips if a timer is already in the queue. Browsers do not create two identical interval timers at the same time.

SetInterval If the time is shorter than the execution time in the function, the actual time after the first execution time should be the total execution time in the function.

The asynchronous code in setInterval does not block the creation of a new timer.

requestAnimationFrame

RequestAnimationFrame is a browser interface for timed loops, similar to setTimeout, that redraw a web page frame by frame.

In Web applications, there are many ways to realize animation effects, such as setTimeout timer in Javascript, Transition and animation in CSS3, and canvas in HTML5. Html5 also provides an API for requesting animations, requestAnimationFrame (requestAnimationFrame).

To understand the principles behind requestAnimationFrame, we first need to look at a few concepts related to it:

1. The page is visible

When the page is minimized or switched to a background TAB, the page is not visible, and the browser fires a VisiBilityChange event and sets the Document. hidden property to true; When you switch to the display state, the page is visible, which also fires a visiBilityChange event and sets the Document. hidden property to false.

2. The animation frame requests a list of callback functions

Each Document has a list of animated frame request callback functions, which can be thought of as a collection of <handlerId, callback> tuples. Where handlerId is an integer that uniquely identifies the tuple’s position in the list; Callback is a callback function.

3. Screen refresh frequency

The rate at which an image is updated on the screen, or the number of times an image appears on the screen per second, is measured in Hertz (Hz). For a typical laptop, the frequency is about 60Hz, depending on screen resolution, screen size and graphics card.

4. Animation principle

According to the principle above, the image you see in front of you is refreshing at a rate of 60 times per second, so high that you don’t feel it refreshing. The essence of animation is to let the human eye see the visual effect of the change caused by the refresh of the image, and this change should be carried out in a coherent and smooth way. So how do you do that?

The screen with a refresh rate of 60Hz is refreshed every 16.7ms. Before each refresh, we move the position of the image to the left by one pixel, i.e. 1px. This way, each image on the screen will be 1px worse than the previous one, so you’ll see it move; Due to the visual stop effect of our human eyes, the impression of the current position of the image stays in the brain is not lost, and then the image is moved to the next position, so you can see the image in smooth movement, which is the visual effect of animation.

RequestAnimationFrame usage

HandlerId = requestAnimationFrame(callback)

(1) Pass in a callback function;

HandlerId is a browser-defined integer greater than 0 that uniquely identifies the position of the callback function in the list.

Browser execution process:

(1) First determine whether the document.hidden property is true, that is, the page will be executed only when it is visible;

(2) The browser clears the animation function of the previous round;

(3) This method returns the value of handlerId and the animation function callback, as >handlerId, callback< into the animation frame request callback sequence;

(4) The browser will iterate over the animation frame request callback function list and execute the corresponding animation function in turn according to the value of handlerId.

To unanimate a function:

cancelAnimationFrame(handlerId)
Copy the code

Here’s an example:

let progress = 0;
// The callback function
function render() {  
  progress += 1; // Change the position of the image
  if (progress < 100) {  // Render recursively before the animation ends
    console.log(progress);
    window.requestAnimationFrame(render); }}// Render first frame
window.requestAnimationFrame(render);
Copy the code

SetTimeout compared to requestAnimationFrame

  1. setTimeout

After understanding the above concept, it is not difficult to find that setTimeout is actually to change the position of the image continuously by setting an interval time, so as to achieve the animation effect. However, animation realized by seTimeout will appear stutter and jitter on some low-end computers. There are two reasons for this phenomenon:

  • The execution time of setTimeout is not fixed. In Javascript, the setTimeout task is put into an asynchronous queue. Only after the main thread is finished, the task in the queue will be checked to see whether the task needs to be executed. Therefore, the actual execution time of setTimeout is usually later than the set time.

  • The refresh frequency is affected by the screen resolution and screen size, so the refresh frequency may be different for different devices. SetTimeout can only set a fixed interval, which may not be the same as the refresh time of the screen.

In both cases, the execution pace of setTimeout is inconsistent with the refresh pace of the screen, resulting in frame loss. So why does being out of step cause frame loss?

First of all, it is important to understand that setTimeout only changes the image properties in memory, and this change will not be updated to the screen until the next time the screen is refreshed. If the two are out of step, it is possible to skip the action in one frame and update the image in the next. Assuming the screen refreshes every 16.7ms and setTimeout sets the image to move 1px to the left every 10ms, the following drawing process will occur:

  • 0ms: The screen is not refreshed, waiting, setTimeout is not executed, waiting;
  • 10ms: The screen is not refreshed, waiting, setTimeout starts to execute and sets the image attribute left=1px;
  • 16.7ms: The screen starts to refresh, the image on the screen moves 1px to the left, setTimeout is not executed, and the waiting process continues;
  • 20ms: The screen is not refreshed, waiting, setTimeout starts execution and sets left=2px;
  • 30ms: The screen is not refreshed, waiting, setTimeout starts execution and sets left=3px;
  • 33.4ms: the screen starts to refresh, the image on the screen moves 3px to the left, setTimeout is not executed, and the waiting process continues;

As can be seen from the drawing process above, the screen does not update the left=2px frame, and the image directly jumps from 1px to 3px, which is a frame loss phenomenon, and this phenomenon will cause animation lag.

  1. requestAnimationFrame

The biggest advantage of requestAnimationFrame over setTimeout is that the system decides when to execute the callback function. To be more specific, if the screen refresh rate is 60Hz, the callback function is executed every 16.7ms, if the refresh rate is 75 hz, the interval becomes 1000/75=13.3ms, in other words, the requestAnimationFrame moves at the pace of the system refresh. It ensures that the callback function is executed only once in each screen refresh interval, thus avoiding frame loss and animation stuttering.

In addition, requestAnimationFrame has the following two advantages:

**CPU energy saving: ** Animation implemented with setTimeout, when the page is hidden or minimized, setTimeout still performs the animation task in the background, because the page is not visible or unavailable at this time, refreshing the animation is meaningless, it is a complete waste of CPU resources. RequestAnimationFrame is completely different. When the page is not active, the screen refresh task of the page is also suspended, so the requestAnimationFrame that follows the pace of the system will also stop rendering. When the page is active, The animation picks up where it left off, effectively saving CPU overhead.

** Function throttling: ** In high frequency events (resize, Scroll, etc.), requestAnimationFrame is used to ensure that functions are executed only once in each refresh interval to prevent multiple functions from being executed in one refresh interval. This ensures smoothness and saves function execution costs. It does not make sense if the function is executed more than once in a refresh interval, because the monitor is refreshed every 16.7ms and multiple draws do not show up on the screen.

  1. Graceful degradation

RequestAnimationFrame currently has compatibility issues and requires different prefixes for different browsers. Therefore, the requestAnimationFrame needs to be wrapped in a gracefully degraded manner, prioritizing advanced features, and then rolling back depending on the browser, until only setTimeout is available. The following code is a polyfill provided by someone on Github. Please refer to github requestAnimationFrame for details.

if (!Date.now)
    Date.now = function() { return new Date().getTime(); };
 
(function() {
    'use strict';
     
    var vendors = ['webkit'.'moz'];
    for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {
        var vp = vendors[i];
        window.requestAnimationFrame = window[vp+'RequestAnimationFrame'];
        window.cancelAnimationFrame = (window[vp+'CancelAnimationFrame'] | |window[vp+'CancelRequestAnimationFrame']);
    }
    if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) // iOS6 is buggy
        || !window.requestAnimationFrame || !window.cancelAnimationFrame) {
        var lastTime = 0;
        window.requestAnimationFrame = function(callback) {
            var now = Date.now();
            var nextTime = Math.max(lastTime + 16, now);
            return setTimeout(function() { callback(lastTime = nextTime); },
                              nextTime - now);
        };
        window.cancelAnimationFrame = clearTimeout;
    }
}());
Copy the code