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)

Introduction to the

Will tell the browser window. RequestAnimationFrame () – you want to perform an animation, and require 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

Note: if you want to in the browser to update until the next redraw the next frame animation, then the callback function itself must once again call window. RequestAnimationFrame ()

In simple terms, if simply call window. A requestAnimationFrame callback function, the browser to the next redrawn, before the callback function called after the process even officially ended, If you still want to again before the next browser redraws the callback function update animation, it needs to be within the callback function recursive call window. RequestAnimationFrame function

usage

The callback function is passed the DOMHighResTimeStamp parameter, which indicates when the callback function currently sorted by requestAnimationFrame() is fired. Multiple callback functions in the same frame will each receive the same timestamp, even if some time has elapsed during the calculation of the workload for the previous callback function. The timestamp is a decimal number in milliseconds with a minimum accuracy of 1ms(1000μs).

Grammar:window.requestAnimationFrame(callback);
Copy the code
window.requestAnimationFrame(function (timeStamp) {
  console.log(timeStamp);
  window.requestAnimationFrame(arguments.callee);
})
Copy the code

Below is our own printed screen refresh interval (60Hz) on the left and the printed time stamp of the callback function in requestAnimationFrame on the right. We see that each callback is executed at a different time interval

Note: Be sure to always calculate the time interval between each call using the first parameter (or some other method to get the current time), otherwise the animation will run faster on screens with high refresh rates. Since the human eye has a short memory of an image, a screen refresh rate of 60Hz is about enough. On devices with high refresh rates, it’s not really necessary to update the image every time the browser refreshes

Here’s an example from MDN: 2s moves one element at a constant speed

const element = document.getElementById('some-element-you-want-to-animate');
let start;

function step(timestamp) {
  if (start === undefined)
    start = timestamp;
  const elapsed = timestamp - start;

  // Use 'math.min ()' here to make sure the element stops at exactly 200px.
  element.style.transform = 'translateX(' + Math.min(0.1 * elapsed, 200) + 'px)';

  if (elapsed < 2000) { // Stop animation after two seconds
    window.requestAnimationFrame(step); }}window.requestAnimationFrame(step);
Copy the code

Supplement: window. RequestAnimationFrame (), the return value is a long integer request ID, is the only the identity of the callback list. It’s a non-zero value, that’s all. You can send this value to the window. The cancelAnimationFrame () to cancel the callback function.

setTimeout

Most of the time, we rely on setTimeout to animate the image – by setting a time interval to dynamically change the position of the image to achieve the effect of animation. However, we will find that animation using setTimetout will appear stuttering and jitter on some low-end machines. 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.
  const start = Date.now()
  setTimeout(() = > {
    console.log(Date.now() - start); / / 1007
  }, 1000)
Copy the code
  • 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: Screen not refreshed, waiting, setTimeout not executed, waiting
  • 10ms: The screen is not refreshed, while waiting, setTimeout starts executing and sets the image property left=1px
  • 16.7ms: The screen starts to refresh, the image on the screen moves 1px to the left, setTimeout is not executed, continue to wait
  • 20ms: The screen is not refreshed, while waiting, setTimeout starts executing and sets left=2px
  • 30ms: Screen 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, continue to wait

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 the phenomenon of frame loss. This phenomenon will cause animation lag, resulting in a bad experience for users

The advantage of requestAnimationFrame

Callbacks are typically executed 60 times per second, but in most browsers that follow W3C recommendations, the number of callbacks usually matches the number of browser screen refreshes. To improve performance and battery life, in most browsers requestAnimationFrame() is suspended while running in a background TAB or hidden < iframe> to improve performance and battery life

  1. CPU energy saving: When using setTimeout to achieve animation, when the page is hidden or minimized, setTimeout is still performing animation tasks in the background, because the page is not visible or unavailable at this time, refreshing animation is meaningless, it is a complete waste of CPU resources. RequestAnimationFrame is completely different. When a page is not active, such as shrinking and hiding, switching pages, etc., the screen refresh task of the page is also suspended, so the requestAnimationFrame that follows the steps of the system will also stop rendering. When the page is reactivated, the animation picks up where it left off, which saves CPU overhead
  2. Function throttling: In high frequency events (resize, Scroll, etc.), to prevent multiple function executions in a refresh interval, requestAnimationFrame is used to ensure that functions are executed only once in each refresh interval. This ensures smooth performance and saves function execution costs. It does not make sense for a function to execute multiple times within a refresh interval, since the monitor typically refreshes every 16.7ms, during which time only the last callback function will be displayed on the screen

compatibility

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 to use only setTimeout. The following code is a polyfill provided by someone on Github. Please refer to github requestAnimationFrame for details

Polyfill is a piece of code (usually JavaScript on the Web) that provides newer functionality to older browsers that it doesn’t have native support for

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

The content of this article is not all original, but the author through the following article

Blog.csdn.net/VhWfR2u02Q/…

Developer.mozilla.org/zh-CN/docs/…