requestAnimationFrame request animation frame

What is requestAnimationFrame

window.requestAnimationFrame, according to the W3C standard, we can call it to tell the browser that an animation needs to be performed, and ask the browser to call the specified callback function to update the animation before the next redraw. So this method needs to pass in a callback function as a parameter, and the callback function will be executed before the browser redraws next time.

What does it mean before the next redraw

How to understand Before the next redraw, first look at these two lines of code

document.body.appendChild(el)
el.style.display = 'none'

This kind of code always makes me feel a little awkward, I will consider whether the element el will “flash by”,

Usually I am used to writing these two lines of code upside down. So in the end will not write a problem? The answer is no.

The process of Js operating dom to browser rendering is as follows:

JavaScript: JavaScript implements animation effects, DOM element operations, etc.

Style (css style): Collect the CSS corresponding to the DOM element.

Layout (layout): Calculate the size and position of the DOM elements displayed on the final screen. Since the element layout of a web page is relative, any change in the position of any element will cause changes in other elements. This process is called reflow.

Paint (drawing): Draw the text, color, image, border and shadow of the DOM element on multiple layers.

Composite (rendering layer merging): Combine layers in a reasonable order and display them on the screen.

Most of the process from Style to Composite is related to the refresh rate of the screen, that is, if the screen refresh rate is 60Hz, the cycle interval of the process from Style to Composite is (1000/60)ms. So if several lines of js code run in the same frame, the state reflected on the page in the next frame is the latest style of dom.

Then the browser rendering process corresponding to the code of demo1 is as follows:

Imagine that each long rectangle is a frame, and the process of Style -> Composite will be executed at the beginning of each frame. If task1, 2, and 3 operate the same attribute of the same dom, then only when executing Style Task3 of the previous frame will take effect.

document.body.appendChild(el)
setTimeout(() => {
   el.style.display = 'none'
}, 20)

If we change dome1 to the above code, and then try to run again, there will be a “flash” phenomenon. The reason is that we execute document.body.appendChild(el) after the first frame is rendered, and the el element will be rendered at the beginning of the second frame, and then execute el.style.display = ‘none’ after the second frame is rendered , the beginning of the third frame will be reflected on the page.

Looking back at the sentence that requestAnimationFrame runs before the next redraw, it can be understood as a function running between Javascript and Style (regarding the differences between browsers and W3C as the standard). If all the animation effects are packaged into RAF, the probability of unsmooth animation (frame drop) due to continuous operation of the same dom will be reduced, and the rendering process will also appear very scientific. Of course, it is inevitable that there will be a few related animation tasks that are leaked outside the RAF callback, but the problem will not be too big. The RAF scheme is as follows:

It should be noted that RAF is a macrotask (macrotask), and the code in the callback should not contain complex algorithms, otherwise the process will still be blocked. Imagine that if the RAF callback algorithm of the first frame is too complicated and the task of the original one frame takes two frames to complete, then the calculation of the second frame will be dragged to the position of the original third frame, which is equivalent to The page refresh rate has been reduced, which may result in a longer total animation time. The diagram is as follows:

basic usage of requestAnimationFrame

Basic syntax:

window. requestAnimationFrame(callback).

parameter:

callback, the function passed in can control the css style of the dom element before the next redraw through js. callback receives a time parameter by default, whose value is equal to performance.now(), which is generally used to control animation effects.

return value:

A non-zero long integer, which is the only identifier in the callback list and has no other meaning, similar to the return value of setTimeout. We can pass this value to window.cancelAnimationFrame() to cancel the callback function.

const black = document. getElementById('black')
        let animationId = null
        let startTime = 0
        let duration = 6000
        let length = 600
        let pass = 0

        const getPosition = (x) => {
            return Math. ceil(length * x)
        }
        const animations = (time) => {

            let left = 0

            if (time) {
                if (time - startTime > duration + 100) {
                    startTime = time - pass
                }
                pass = time - startTime
                left = getPosition(pass / duration)
            }

            if (left > length) {
                black.style.left = `${length}px`
                cancelAnimationFrame(animationId)
            } else {
                black.style.left = `${left}px`
                animationId = requestAnimationFrame((timeStamp) => {
                    if (!startTime) startTime = timeStamp
                    animations(timeStamp)
                })
            }
        }
        black. addEventListener('click', () => {
            animations()
        })

It is actually very simple to use setInterval or setTimeout in the above code, but this kind of constant speed animation is the most unpretentious kind, and the difference between uniform speed animation is obvious. I glanced at the jquery code a long time ago. Its animation is done with setTimeout. I heard that it was changed to RAF later. setTimeout cannot control the frequency of page refreshes, and the calculation amount and animation effect of animations with uniform speed changes or animations with complex speed changes are not as good as RAF.

Pros and cons of requestAnimationFrame

Advantages:

  1. After browser optimization, the animation is smoother.

  2. The interval time is fixed, and only the change of each frame needs to be calculated.

  3. When the window is not active, the animation will stop, saving computing resources.

  4. Combining the advantages of the first three points, this solution saves power and is more friendly to mobile terminals.

Disadvantages:

  • The timing of invoking RAF in different browsers is different. The rendering process of some browsers is as follows:

    Although the calling timing is different, the difference is not very big, but there will be a frame delay every time the animation starts to execute. I tried three commonly used browsers and tried it.

    firefox: execute RAF before Style;
    Chrom: It is found on the Internet that RAF is executed before Style;
    Safari: RAF is executed after Compsite;

  • requestAnimationFrame compatibility issues and solutions

Search for this api on caniuser, the support rate is still very impressive, but in order to satisfy individual monsters, you need to add some code to solve it.

The following code is searched, and it is not so difficult to read. In fact, it is to judge whether the window object has the requestAnimationFrame method in the current environment. If not, try adding the prefixes of ‘webkit’ and ‘moz’. If it still does not If so, use setTimeout to simulate one. The same is true for cancelAnimationFrame. If there is no one, use clearTimeout to simulate it.

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;
    }
}());