Detailed explanation of requestAnimationFrame - js performance optimization

requestAnimationFrame requests animation frame
which is a browser macro task

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 repaints. It returns an integer representing the number of the timer, this value can be passed to cancelAnimationFrame to cancel the execution of this function

requestAnimationFrame Features
[1] requestAnimationFrame will gather all the DOM operations in each frame and complete it in one redraw or reflow, and the time interval of redraw or reflow closely follows the refresh rate of the browser, if the system draw rate If it is 60Hz, then the callback function will be executed again in 16.7ms. If the drawing frequency is 75Hz, then the interval time becomes 1000/75=13.3ms. In other words, the execution pace of requestAnimationFrame follows the drawing frequency of the system. It can ensure that the callback function is executed only once in each drawing interval of the screen, so that it will not cause frame loss, and will not cause the animation to freeze.

[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 at runtime, and if the page is not activated, the animation will automatically pause, effectively saving CPU overhead.

The problem with setTimeout and setInterval compared to
setTimeout and setInterva is that they are not precise. Their internal operating mechanism determines the time interval, and the 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 will wait for the previous tasks to complete before executing

requestAnimationFrame adopts the system time interval to maintain the best drawing efficiency, and will not cause overdrawing and increase overhead because the interval is too short; nor will the animation freeze because the interval is too long, so that various web animation effects can be achieved There is a unified refresh mechanism, which saves system resources, improves system performance, and improves visual effects

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

//简单兼容
if (!window.requestAnimationFrame) {
    requestAnimationFrame = function(fn) {
        setTimeout(fn, 17);
    };    
}
//严格兼容 , 因为setTimeout内部运行也需要时间,以及需要给回调的第一个参数返回时间戳
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;
    }
}

Application Scenario

1. Monitor the scroll function

The listener function of the page scrolling event (scroll) is very suitable for using this api, which is postponed until the next re-rendering.

$(window).on('scroll', function () {
  window.requestAnimationFrame(scrollHandler)
})

Smooth scroll to top of page

const scrollToTop = () => { 
  const c = document.documentElement.scrollTop || document.body.scrollTop 
  if (c > 0) {  
    window.requestAnimationFrame(scrollToTop) 
    window.scrollTo(0, c - c / 8) 
  }
}

scrollToTop()

 

2. Mass data rendering

For example, to render 100,000 pieces of data, there are mainly the following methods:

(1) Using a timer

//需要插入的容器
let ul = document.getElementById('container')
// 插入十万条数据
let total = 100000
// 一次插入 20 条
let once = 20
//总页数
let page = total / once
//每条记录的索引
let index = 0
//循环加载数据
function loop(curTotal, curIndex) { 
  if (curTotal <= 0) {  
    return false 
  }  
  //每页多少条
  let pageCount = Math.min(curTotal, once) 
  setTimeout(() => {  
    for (let i = 0; i < pageCount; i++) { 
      let li = document.createElement('li')    
      li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)    
      ul.appendChild(li)  
    }  
    loop(curTotal - pageCount, curIndex + pageCount) 
  }, 0)
}
loop(total, index)

 (2) Use requestAnimationFrame

//需要插入的容器
let ul = document.getElementById('container')
// 插入十万条数据
let total = 100000
// 一次插入 20 条
let once = 20
//总页数
let page = total / once
//每条记录的索引
let index = 0
//循环加载数据
function loop(curTotal, curIndex) {
  if (curTotal <= 0) {
    return false
  }
  //每页多少条
  let pageCount = Math.min(curTotal, once)
  window.requestAnimationFrame(function () {
    for (let i = 0; i < pageCount; i++) {
      let li = document.createElement('li')
      li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
      ul.appendChild(li)
    }
    loop(curTotal - pageCount, curIndex + pageCount)
  })
}
loop(total, index)

 3. Monitoring freeze method
Calculate the FPS of the web page every second, obtain a column of data, and then analyze it. The popular explanation is that some JS codes are executed regularly through the requestAnimationFrame API. If the browser freezes, the frequency of rendering cannot be well guaranteed. The frame in 1s cannot reach 60 frames, which can indirectly reflect the rendering frame rate of the browser.
 

var lastTime = performance.now()
var frame = 0
var lastFameTime = performance.now()
var loop = function (time) {
  var now = performance.now()
  var fs = now - lastFameTime
  lastFameTime = now
  var fps = Math.round(1000 / fs)
  frame++
  if (now > 1000 + lastTime) {
    var fps = Math.round((frame * 1000) / (now - lastTime))
    frame = 0
    lastTime = now
  }
  window.requestAnimationFrame(loop)
}

Attach another case description:

Requirement: Use js to realize an infinite loop animation.

The first thing that comes to mind is the timer

<!doctype html>
<html lang="en">
<head>
    <title>Document</title>
    <style>
        #e{
            width: 100px;
            height: 100px;
            background: red;
            position: absolute;
            left: 0;
            top: 0;
            zoom: 1;
        }
    </style>
</head>
<body>
<div id="e"></div>
<script>
 
 
    var e = document.getElementById("e");
    var flag = true;
    var left = 0;
 
    function render() {
        if(flag == true){
            if(left>=100){
                flag = false
            }
            e.style.left = ` ${left++}px`
        }else{
            if(left<=0){
                flag = true
            }
            e.style.left = ` ${left--}px`
        }
    }
    setInterval(function(){
         render()
    },1000/60)
 
</script>
</body>
</html>

 

It can be said to be a perfect realization!

As for why the time interval is 1000/60, this is because most screen rendering time intervals are 60 frames per second.

Since setInterval can handle it, why use requestAnimationFrame ? The most intuitive feeling is that the person who added the API is a god-level genius, and I can only doubt myself.

So searching for related issues found the following two things

There are two main advantages of requestAnimationFrame over setTimeout and setInterval:

1. requestAnimationFrame will gather all DOM operations in each frame and complete it in one redraw or reflow, and the time interval of redraw or reflow closely follows the refresh frequency of the browser. Generally speaking, this frequency is 60 frames per second.

2. In hidden or invisible elements, requestAnimationFrame will not redraw or reflow, which of course means less cpu, gpu and memory usage.

Go directly to the code:

<!doctype html>
<html lang="en">
<head>
    <title>Document</title>
    <style>
        #e{
            width: 100px;
            height: 100px;
            background: red;
            position: absolute;
            left: 0;
            top: 0;
            zoom: 1;
        }
    </style>
</head>
<body>
<div id="e"></div>
<script>
 
 
    var e = document.getElementById("e");
    var flag = true;
    var left = 0;
 
    function render() {
        if(flag == true){
            if(left>=100){
                flag = false
            }
            e.style.left = ` ${left++}px`
        }else{
            if(left<=0){
                flag = true
            }
            e.style.left = ` ${left--}px`
        }
    }
 
    //requestAnimationFrame效果
    (function animloop() {
        render();
        window.requestAnimationFrame(animloop);
    })();
 
</script>
</body>
</html>

I didn't add the compatible writing of each browser, only the usage is mentioned here.

The effect is realized, but I think of two problems.

1. How to stop requestAnimationFrame? Is there a similar method like clearInterval?

The first question: the answer is certain and there must be: cancelAnimationFrame() receives a parameter requestAnimationFrame returns an id by default, and cancelAnimationFrame only needs to pass in this id to stop.

<!doctype html>
<html lang="en">
<head>
    <title>Document</title>
    <style>
        #e{
            width: 100px;
            height: 100px;
            background: red;
            position: absolute;
            left: 0;
            top: 0;
            zoom: 1;
        }
    </style>
</head>
<body>
<div id="e"></div>
<script>
 
 
    var e = document.getElementById("e");
    var flag = true;
    var left = 0;
    var rafId = null
 
 
    function render() {
        if(flag == true){
            if(left>=100){
                flag = false
            }
            e.style.left = ` ${left++}px`
        }else{
            if(left<=0){
                flag = true
            }
            e.style.left = ` ${left--}px`
        }
    }
 
    //requestAnimationFrame效果
    (function animloop(time) {
        console.log(time,Date.now())
        render();
        rafId = requestAnimationFrame(animloop);
        //如果left等于50 停止动画
        if(left == 50){
            cancelAnimationFrame(rafId)
        }
    })();
 
    //setInterval效果
    // setInterval(function(){
    //     render()
    // },1000/60)
 
</script>
</body>
</html>

 

 

2. If I want to reduce the animation frequency, why not consider speeding it up?

This is a little more troublesome

By default, the execution frequency of requestAnimationFrame is 1000/60, which is about one more execution in 16ms.

What if we want to execute it every 50ms?

The execution conditions of requestAnimationFrame are similar to recursive calls (speaking similarly). Don't bite me. In this case, can we set a time interval before executing it? Of course, we don’t consider something as low as the timer, and we have already abandoned it and used rAF (it was almost over when I remembered that the abbreviation is too fucking long). This
idea comes from a project I worked on IM a few years ago. , The server pushes the message to reduce the size of the package without giving a timestamp. We all know this as the front-end. Although we are awesome, the users are even better. If the time is changed, it will not be fun.

The solution is to record a time difference when communicating with the server, (the time difference is equal to the server time - local time) regardless of positive or negative, we only need this time difference. In this way, whenever we receive or send a message, we add the local time and the price difference. In this way, it can be guaranteed to be consistent with the time of the server, isn't the idea very awesome haha.

Withdrawing for a long time, we use the above ideas to solve the problem of changing the interval of rAF

upper code

<!doctype html>
<html lang="en">
<head>
    <title>Document</title>
    <style>
        #e{
            width: 100px;
            height: 100px;
            background: red;
            position: absolute;
            left: 0;
            top: 0;
            zoom: 1;
        }
    </style>
</head>
<body>
<div id="e"></div>
<script>
 
 
    var e = document.getElementById("e");
    var flag = true;
    var left = 0;
    //当前执行时间
    var nowTime = 0;
    //记录每次动画执行结束的时间
    var lastTime = Date.now();
    //我们自己定义的动画时间差值
    var diffTime = 40;
 
    function render() {
        if(flag == true){
            if(left>=100){
                flag = false
            }
            e.style.left = ` ${left++}px`
        }else{
            if(left<=0){
                flag = true
            }
            e.style.left = ` ${left--}px`
        }
    }
 
    //requestAnimationFrame效果
    (function animloop() {
        //记录当前时间
        nowTime = Date.now()
        // 当前时间-上次执行时间如果大于diffTime,那么执行动画,并更新上次执行时间
        if(nowTime-lastTime > diffTime){
            lastTime = nowTime
            render();
        }
        requestAnimationFrame(animloop);
 
    })()
</script>
</body>
</html>

Effect: 

 

Guess you like

Origin blog.csdn.net/hyupeng1006/article/details/128861813