requestAnimationFrame

In web applications, there are many ways to achieve animation effects. In Javascript, it can be achieved through the timer setTimeout. In CSS3, transition and animation can be used. Canvas in HTML5 can also be implemented. In addition, html5 also provides an API specifically for requesting animations, which is requestAnimationFrame. As the name suggests, it requests animation frames. In order to deeply understand the principles behind requestAnimationFrame, we first need to understand several concepts related to it:

1. Screen refresh frequency

That is, the speed at which the image updates on the screen, that is, the number of times the image on the screen appears per second, and its unit is Hertz (Hz). For a general laptop, this frequency is about 60Hz, which can be viewed and set by right-clicking on the desktop -> Screen Resolution -> Advanced Settings -> Monitor. The setting of this value is affected by the screen resolution, screen size and graphics card. In principle, it can be set to a value that is comfortable for the eyes.

There are two common monitors on the market, namely CRT and LCD. CRT is a traditional monitor, and LCD is what we often call a liquid crystal monitor.

CRT is a display using a cathode ray tube. The graphic image on the screen is composed of phosphor dots that emit light due to the impact of electron beams. Since the phosphors in the picture tube emit light for a very short time after being struck by electron beams, The electron beam must constantly hit the phosphor to keep it glowing. The number of times the electron beam hits the phosphor per second is the screen refresh frequency.

For LCD, there is no refresh frequency problem, it does not need to be refreshed at all. Because each pixel in the LCD continuously emits light until the non-luminous voltage changes and is sent to the controller, the LCD does not have flicker caused by the electron beam hitting the phosphor.

Therefore, when you are doing nothing in front of the computer screen, the monitor will continue to update the image on the screen at a frequency of 60 times per second. Why can’t you feel this change? That’s because human eyes have a visual retention effect, that is, the impression of the previous picture left in the brain has not disappeared, and the next picture follows immediately, with only an interval of 16.7ms (1000/60≈16.7) , so it makes you mistakenly think that the image on the screen is static. The feeling the screen gives you is right. Just imagine, if the refresh rate becomes 1 time/second, the images on the screen will flicker severely, which can easily cause eye fatigue, soreness, dizziness and other symptoms. .

2. Principles of animation

Based on the above principle, we know that the image you see in front of you is refreshing at a frequency of 60 times per second. Because the refresh frequency is very high, you do not feel that it is refreshing. The essence of animation is to let people see the visual effect of changes caused by the image being refreshed. This change must be transitioned in a coherent and smooth way. So how can we achieve this effect?

A screen with a refresh rate of 60Hz refreshes every 16.7ms. Before each refresh of the screen, we move the position of the image one pixel to the left, that is, 1px. In this way, the position of the image displayed on the screen each time is 1px worse than the previous one, so you will see the image moving; due to the visual retention effect of our human eyes, the impression that the image at the current position remains in the brain has not disappeared. , and then the image is moved to the next position, so you will see the image moving smoothly. This is the animation formed in the visual effect.

3. setTimeout

After understanding the above concepts, it is not difficult to find that setTimeout actually continuously changes the position of the image by setting an interval to achieve the animation effect. However, we will find that the animation implemented using seTimeout will appear stuck and jittery on some low-end machines. There are two reasons for this phenomenon:

  • The execution time of setTimeout is not certain. In Javascript, the setTimeout task is put into an asynchronous queue. Only when the task on the main thread is executed, will it check whether the task in the queue needs to start executing. Therefore, the actual execution time of setTimeout is generally longer than its setting. The time is later.

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

The above two situations will cause the execution pace of setTimeout to be inconsistent with the refresh pace of the screen, resulting in frame loss. So why does inconsistent pace cause frame loss?

First of all, you must understand that the execution of setTimeout only changes the image attributes in the memory. This change must wait until the next refresh of the screen before it is updated to the screen. If the pace of the two is inconsistent, it may cause the operation of an intermediate frame to be skipped and directly update the image of the next frame. Assuming that the screen is refreshed 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 and is waiting. SetTimeout is not executed and is waiting;

  • 10ms: The screen is not refreshed and while waiting, setTimeout starts executing 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 continues to wait;

  • 20ms: The screen is not refreshed and while waiting, setTimeout starts executing and sets left=2px;

  • 30ms: The screen is not refreshed and while waiting, setTimeout starts executing 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 continues to wait;

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

4. requestAnimationFrame

Compared with setTimeout, the biggest advantage of requestAnimationFrame is that the system determines the execution timing of the callback function. Specifically, if the screen refresh rate is 60Hz, then the callback function will be executed every 16.7ms. If the refresh rate is 75Hz, then the time interval becomes 1000/75=13.3ms. In other words, requestAnimationFrame The pace follows the refresh pace of the system. It can ensure that the callback function is only executed once during each refresh interval of the screen, so that it will not cause frame loss or freeze in the animation.

The call to this API is very simple, as shown below:

var progress = 0;

//Callback

function render() {

progress + = 1; //Modify the position of the image

if (progress < 100) {

//Render recursively before the animation ends

window.requestAnimationFrame(render);

}

}

//First frame rendering

window.requestAnimationFrame(render);

In addition, requestAnimationFrame has the following two advantages:

  • CPU energy saving: For animations implemented using setTimeout, when the page is hidden or minimized, setTimeout still performs animation tasks in the background. Since the page is invisible or unavailable at this time, refreshing the animation is meaningless and a complete waste of CPU resources. . The requestAnimationFrame is completely different. When the page processing is not activated, the screen refresh task of the page will also be suspended by the system. Therefore, the requestAnimationFrame that follows the system’s pace will also stop rendering. When the page is activated, the animation will start from the last time. Execution continues where it left off, effectively saving CPU overhead.

  • Function throttling: In high-frequency events (resize, scroll, etc.), in order to prevent multiple function executions within a refresh interval, use requestAnimationFrame to ensure that the function is only executed once within each refresh interval, thus ensuring smoothness performance, and can also better save the cost of function execution. It makes no sense to execute the function multiple times within a refresh interval, because the display refreshes every 16.7ms, and multiple drawings will not be reflected on the screen.

5. Graceful downgrade

Because requestAnimationFrame currently has compatibility issues, and different browsers require different prefixes. Therefore, requestAnimationFrame needs to be encapsulated through graceful degradation, giving priority to the use of advanced features, and then rolling back according to the conditions of different browsers, until only setTimeout can be used. The following code is the polyfill provided by someone on github. For details, please refer to the github code requestAnimationFrame (https://github.com/darius/requestAnimationFrame)

if (!Date.now)

Date.now = function() { return new Date().getTime(); };

(function() {

‘use strict’;

var vendors = [‘webkit’, ‘moz’];

for (var i = 0; i < vendors.length & amp; & amp; !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;

}

}());

Unlike setTimeout and setInterval, requestAnimationFrame does not require setting a time interval. What’s the benefit? Why is requestAnimationFrame called an artifact? This article will introduce in detail the new timer requestAnimationFrame in HTML5

Introduction

Timers have always been the core technology of JavaScript animation. The key to writing an animation loop is to know how long the delay time is. On the one hand, the loop interval must be short enough to make different animation effects appear smooth; on the other hand, the loop interval must be long enough to ensure that the browser has the ability to render the changes.

The refresh rate of most computer monitors is 60Hz, which is roughly equivalent to redrawing 60 times per second. Most browsers will limit redraw operations to no more than the display’s redraw frequency, because even beyond that frequency the user experience will not be improved. Therefore, the optimal loop interval for the smoothest animation is 1000ms/60, which is approximately 16.6ms

The problem with setTimeout and setInterval is that they are not precise. Their internal operating mechanism determines that the time interval parameter actually just specifies the time to add the animation code to the browser UI thread queue to wait for execution. If other tasks have been added to the front of the queue, the animation code must wait until the previous task is completed before executing it.

requestAnimationFrame uses the system time interval to maintain the best drawing efficiency. It will not cause over-drawing and increase overhead because the interval is too short. It will also not cause the animation to be stuck and unsmooth because the interval is too long, allowing various web page animation effects to be achieved. There is a unified refresh mechanism to save system resources, improve system performance and improve visual effects.

Features

[1] requestAnimationFrame will concentrate all DOM operations in each frame and complete them in one redraw or reflow, and the time interval of redraw or reflow closely follows the refresh frequency of the browser.

[2] In hidden or invisible elements, requestAnimationFrame will not redraw or reflow, which of course means less CPU, GPU and memory usage

[3] requestAnimationFrame is an API provided by the browser specifically for animation. The browser will automatically optimize the method call during runtime, and if the page is not active, the animation will automatically pause, effectively saving CPU overhead.

Use

The usage of requestAnimationFrame is very similar to settimeout, except that there is no need to set the time interval. requestAnimationFrame takes a callback function as a parameter. This callback function will be called before the browser redraws. It returns an integer representing the timer number. This value can be passed to cancelAnimationFrame to cancel the execution of this function.

requestID = requestAnimationFrame(callback); 
//Console outputs 1 and 0
var timer = requestAnimationFrame(function(){
    console.log(0);
});
console.log(timer);//1

The cancelAnimationFrame method is used to cancel the timer

//The console outputs nothing
var timer = requestAnimationFrame(function(){
    console.log(0);
});
cancelAnimationFrame(timer);

You can also directly use the return value to cancel

var timer = requestAnimationFrame(function(){
    console.log(0);
});
cancelAnimationFrame(1);

Compatible

IE9-browser does not support this method, you can use setTimeout for compatibility

[Simple and compatible]

if (!window.requestAnimationFrame) {
    requestAnimationFrame = function(fn) {
        setTimeout(fn, 17);
    };
}

【Strictly compatible】

Copy code

if(!window.requestAnimationFrame){
    var lastTime = 0;
    window.requestAnimationFrame = function(callback){
        var currTime = new Date().getTime();
        var timeToCall = Math.max(0,16.7-(currTime - lastTime));
        var id = window.setTimeout(function(){
            callback(currTime + timeToCall);
        },timeToCall);
        lastTime = currTime + timeToCall;
        return id;
    }
}

Copy code

if (!window.cancelAnimationFrame) {
    window.cancelAnimationFrame = function(id) {
        clearTimeout(id);
    };
}

Application

Now use the three methods setInterval, setTimeout and requestAnimationFrame to create a simple progressive effect.

【1】setInterval

Copy code

<div id="myDiv" style="width: 0;height: 20px;line-height: 20px;">0%</div>
<button id="btn">run</button>
<script>
var timer;
btn.onclick = function(){
    clearInterval(timer);
    myDiv.style.width = '0';
    timer = setInterval(function(){
        if(parseInt(myDiv.style.width) < 500){
            myDiv.style.width = parseInt(myDiv.style.width) + 5 + 'px';
            myDiv.innerHTML = parseInt(myDiv.style.width)/5 + '%';
        }else{
            clearInterval(timer);
        }
    },16);
}
</script>

Copy code

【2】setTimeout

Copy code

<div id="myDiv" style="width: 0;height: 20px;line-height: 20px;">0%</div>
<button id="btn">run</button>
<script>
var timer;
btn.onclick = function(){
    clearTimeout(timer);
    myDiv.style.width = '0';
    timer = setTimeout(function fn(){
        if(parseInt(myDiv.style.width) < 500){
            myDiv.style.width = parseInt(myDiv.style.width) + 5 + 'px';
            myDiv.innerHTML = parseInt(myDiv.style.width)/5 + '%';
            timer = setTimeout(fn,16);
        }else{
            clearTimeout(timer);
        }
    },16);
}
</script>

Copy code

【3】requestAnimationFrame

Copy code

<div id="myDiv" style="width: 0;height: 20px;line-height: 20px;">0%</div>
<button id="btn">run</button>
<script>
var timer;
btn.onclick = function(){
    myDiv.style.width = '0';
    cancelAnimationFrame(timer);
    timer = requestAnimationFrame(function fn(){
        if(parseInt(myDiv.style.width) < 500){
            myDiv.style.width = parseInt(myDiv.style.width) + 5 + 'px';
            myDiv.innerHTML = parseInt(myDiv.style.width)/5 + '%';
            timer = requestAnimationFrame(fn);
        }else{
            cancelAnimationFrame(timer);
        }
    });
}
</script>

Copy code