requestAnimationFrameFPS

Most monitors have a refresh rate of 60 times per second, which means the picture feels smooth to the human eye at 60 FPS. The page of the browser is also rendered frame by frame, so to ensure smooth page, the time interval of each frame rendering should not exceed 1000/60 ≈ 16.7ms, if three consecutive frames are less than 24FPS, it is considered that there is a lag. Of course when I play LOL I feel like it’s a lot worse than 50fps. That’s why I bought the rainbow 3070

RequestAnimationFrameMDN explanation:

Tell the browser window. RequestAnimationFrame () – you want to perform an animation, and required the browser until the next redraw calls the specified callback function to update the animation. This method takes as an argument a callback function that is executed before the browser’s next redraw. This will cause the browser to call the animation function you passed into the method (that is, your callback) before the next redraw. Callbacks are typically executed 60 times per second. Raf will centralize dom changes to avoid repeated drawing.

Raf registered callbacks are executed before each browser redraw, usually with a number that matches the browser screen refresh, meaning my 60fps monitor is refreshed every 16.67ms, so we can calculate the current FPS in the callback.

// If three consecutive draws are below 24 frames/SEC, it is considered to be stuck
  const smoothThreshold = 1000 / 24;
  function calcFps() {
    let now = performance.now();
    let frame = 0;
    let fps = 0;
    function checkFps() {
      frame++;
      if (frame >= 3) {
        console.log("The page has dropped frames.");
      }
      // If the next render frame exceeds the fluency threshold, it is considered to be stuck
      if (performance.now() - now < smoothThreshold) {
        fps = (frame * 1000) / (performance.now() - now);
        frame = 0;
      }
      now = performance.now();
      log(fps);
      window.requestAnimationFrame(checkFps);
    }
    requestAnimationFrame(checkFps);
  }
  window.requestAnimationFrame(calcFps);
  
   // Throttle controls the print frequency
  function log (fps) {
      return throttle(() = > console.log(fps));
  }
 
  function throttle(fn, timeout = 600) {
    let timer = null;
    let now = performance.now();
    return (. args) = > {
      clearTimeout(timer);
      timer = setTimeout(() = > {
        if (performance.now() - now >= timeout) {
          fn.call(null. args); now = performance.now(); }},Math.max(timeout - (performance.now() - now)));
    };
  }
Copy the code

Why not usesetTimeoutCalculate the FPS?

There are two reasons:

  • Because setTimeout is affected by the event queue, the setTimeout callback is not pushed until the task stack is empty, which results in the setTimeout callback being executed later than the set interval. RequestAnimationFrame callback is scheduled by the browser and executed automatically before the frame is drawn. There is no accuracy problem.

  • The screen drawing frequency of different devices may be different. For example, the screen drawing time of 120hz is 8.4ms. SetTimeout can only set a fixed interval, which may not be the same as the screen refresh time.

userequestAnimationFrameandsetTimeoutDo the animation

The way to achieve animation can be CSS or JS, first discuss a performance of JS animation. RequestAnimationFrame and setTimeout are compared for animation. Below, two small balls are used for uniform motion. SetTimeout is used for no. 1 ball and RAF is used for No. 2 ball.

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <title>Document</title>
    <style>
      .span2..span4 {
        color: white;
        text-align: center;
        line-height: 30px;
        height: 30px;
        width: 30px;
        background-color: red;
        border-radius: 50%;
        margin-bottom: 50px;
      }
    </style>
  </head>

  <body>
    <div class="span2">1</div>
    <div class="span4">2</div>
    <script>
      // Set it to 60 frames
      const threshold = 1000 / 60;
      const duration = 5000;
      // Move forward each frame
      const step = 1000 / (duration / threshold);
      
      const span2 = document.querySelector(".span2");
      const span4 = document.querySelector(".span4");

      let n = performance.now();

      function moveSetTimeout(el, count = 0) {
        setTimeout(() = > {
          count++;
          // Tests whether the callback executes later than the set interval
          if (performance.now() - n > 20) {
            console.log("setimeout callback trigger:", performance.now() - n);
          }
          n = performance.now();
          el.style.transform = `translateX(${count * step}px)`;
          if (count * step < 1000) {
            moveSetTimeout(el, count);
          }
        }, threshold);
      }

      function moveRaf(el, count = 0) {
        const run = () = > {
          count++;
          el.style.transform = `translateX(${count * step}px)`;
          if (count * step < 1000) {
            window.requestAnimationFrame(run); }};window.requestAnimationFrame(run);
      }

      moveSetTimeout(span2);
      window.requestAnimationFrame(() = > moveRaf(span4));
    </script>
  </body>
</html>

Copy the code

Motion giFs:

It can be seen that raf2 ball moves smoothly, while No. 1 ball shakes and loses frames. In addition, the displacement distance of No. 1 ball is a little behind that of No. 2 ball, and there is no overlap in position. The console prints multiple times. Let’s analyze why

Cause analysis,

  • Why is the timing of timer callback incorrect
  • Why is there jitter

Why is the timing of timer callback incorrect

Due to the influence of event loop, even if the interval of 16.67ms is set, the timer callback is a macro task, and the macro task will not be pushed before the synchronization task stack is cleared. When the main thread executes the synchronization task for a long time, the timer callback execution time will also be delayed, resulting in the interval of callback execution greater than 16.67ms

Why is there jitter

Because the DOM changes to the setTimeout operation are executed before the browser’s next redraw, otherwise they just stay in memory. Because the timer callback cannot guarantee the overlap with the browser redraw time, it will lead to a frame is not drawn, directly draw the next frame, there is a jump, so it will jitter. The following example moves the DOM 3.3px to the left per frame;

const threshold = 1000 / 60;
const duration = 5000;
// Move forward each frame
const step = 1000 / (duration / threshold); / / 3.33 px
Copy the code
// The actual interval between each timer callback being triggered
setimeout callback call: 17.79
setimeout callback call: 21.60
setimeout callback call: 16.79
setimeout callback call: 16.79
setimeout callback call: 19.29
Copy the code

Make the following table according to the fixed time interval of the console above.

Time/Type SetTimeout Indicates the offset Raf offset distance Whether the browser draws
0ms 0 0 no
16.7 ms 0 Shift 3.3px to the left is
17.79 ms The callback executes, sets the left offset to 3.3px, and waits for the next drawing 0 no
33.4 ms Shift 3.3px to the left Shift 3.3px to the left is
39.39 The callback executes, sets the left offset to 3.3px, and waits for the next drawing 0 no
50.10 ms Shift 3.3px to the left Shift 3.3px to the left is
56.18 ms The callback executes, sets the left offset to 3.3px, and waits for the next drawing 0 no
66.80 ms Shift 3.3px to the left Shift 3.3px to the left is

As can be seen from the above table, when the browser renders the fourth frame, the timer renders the displacement of three frames, but RAF keeps the same, and the deviation will be gradually larger later. A certain frame of the timer does not render, but directly renders a later frame, causing the problem of jumping. So try not to use timer animation, if you want to use JS animation, should use RAF.

The timer andrafDifference and

In common

  • Registered callbacks are macro tasks governed by event-loop
  • Run in background TAB or hiddeniframeIs paused to improve performance and battery life

Why is it controlled by event-loop? Look at the following code

  moveSetTimeout(span2);
  window.requestAnimationFrame(() = > moveRaf(span4));
  setTiemout(() = > {
      const now = performance.now();
      // The js thread is directly suspended for 2000ms
      while(performance.now() - now < 2000) {}},1000)

Copy the code

This is because the JS task stack is suspended for two seconds, while the JS thread and the browser UI thread are mutually exclusive, and the RAF and timer callbacks are waiting until the task stack in the JS thread is cleared. Therefore, even if raf is used for animation, such scenes need to be considered, otherwise RAF can also make very poor animation.

differences

  • Raf can ensure that the callback function is executed before the next browser redraw, while the timer callback execution time is not synchronized with the browser draw
  • Raf intelligently follows the screen refresh rate to ensure callbacks are executed, while timers need to be manually set at intervals
  • Raf needIE10Supported by the above version, timer unlimited

Animations use CSS3 instead of JS as much as possible

Using Css3 instead of JS for animation has the following benefits:

  1. Disengaging from the JS thread (after the first rendering), even if the JS thread is suspended, does not affect the animation because the animation is running on the composite thread
  2. Can take advantage of hardware GPU acceleration
  3. Css3 animations do not trigger browser redraw rearrangements

Details can be found here: main thread and composite thread

The first point can be verified with the following code: the JS thread hangs for 2 seconds without affecting the rendering of the animation.

<! DOCTYPEhtml>
<html lang="en">
<head>
    <title>Document</title>
    <style>
        .span1 {
            height: 30px;
            display: inline-block;
            width: 30px;
            background-color: red;
            border-radius: 50%;
            margin-bottom: 20px;
            animation: 5s linear move;
            color: white;
            text-align: center;
            line-height: 30px;
        }
        @keyframes move {
            0% {
                transform: translateX(0);
            }

            100% {
                transform: translateX(calc(100vw)); }}</style>
</head>

<body>
    <span class="span1">1</span>
    <script>
        setTimeout(() = > {
            let now = performance.now();
            while(performance.now() - now < 2000){}
        }, 1000)
    </script>
</body>

</html>
Copy the code

This is why CSS animations can still execute even if the JS main thread is stuck.

The third point can be passedpeformancePanel recording view

conclusion

Through the analysis and comparison, a better understanding of raf and timer do animation difference, along with the review of event-loop and browser threads.

Reference:

  • Front-end performance optimization — Transform and Position
  • Raf know how much