Animation using requestAnimationFrame

How to implement an animation

Let's implement the simplest requirement by moving an element evenly from the left side of the screen to the right side of the screen.

(1)css animation

css implementation is the most reasonable and efficient.

@keyframes move_animation1 {
  0% { left: 0px; }
  100% { left: calc(100% - 60px); }
}
@keyframes move_animation {
  0% { transform: translateX(0); }
  50% { transform: translateX(250px); }
  100% { transform: translateX(500px)); }    
}
.animate-div {
  width: 60px;
  height: 40px;
  border-radius: 5px;
  background: #92B901;
  left: 0;
  position: absolute;
  transform: translateZ(0);
  -webkit-transform: translateZ(0);
  animation: move_animation 5s linear 2s infinite alternate;
}

Note: transform:translateZ(0); It is used to start chrome GPU acceleration and solve the animation "Caton". Using transform in animation has better performance than left/top, and can reduce browser repaint.

(2) What if it is implemented with JS

The first thought is setInterval/setTimeout. The principle is to use the visual residue of human eyes and the refresh of computer screen to gradually change the position of elements in a coherent and smooth way, and finally realize the effect of animation.

The common screen refresh frequency is 60Hz, and some E-sports screens are 144Hz. Taking the common refresh frequency as an example, 60Hz means that the screen is refreshed every 1000 / 60 ≈ 16.7ms, so we set the interval of setInterval to 16.7ms:

const animateDiv = document.querySelector('.animate-div')
let i = 0
let inter = setInterval(() => {
  animateDiv.style.left = 1/3 * (++i) + '%'
  if (i === 300) clearInterval(inter)
}, 16.7)

setInterval/setTimeout has two problems:

  • The execution time of setTimeout is not certain. In Javascript, the setTimeout task is put into the asynchronous queue. Only after the task on the main thread is executed will it be checked whether the task in the queue needs to be executed. Therefore, the actual execution time of setTimeout is generally later than its set time.
  • The refresh rate is affected by the screen resolution and screen size, so the screen refresh rate 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.

In both cases, the execution step of setTimeout is inconsistent with the refresh step of the screen, resulting in frame loss. Although we set the time interval to 16.7ms in the above code, the phenomenon of frame loss cannot be completely avoided.

(3)requestAnimationFrame

What's the biggest difference between requestAnimationFrame and setTimeout/setInterval is that the system's own refresh mechanism decides when to call the animation function. Developers only need to define animated functions, which will be called before the browser redraws.

Introduction to requestAnimationFrame

requestAnimationFrame receives a callback function as a parameter, DOMHighResTimeStamp Indicates when the callback function currently sorted by requestAnimationFrame() is triggered. In the callback function, the time stamp is passed as a parameter. The time stamp is a decimal number, in milliseconds, with a minimum accuracy of 1ms.

const animateDiv = document.querySelector('.animate-div')
let start = null

// Callback function
function step(timestamp) {
    if (!start) start = timestamp
    let progress = timestamp - start
    animateDiv.style.left = progress + 'px'
    if (progress < 350) {
        // Render recursively before the animation ends
        window.requestAnimationFrame(step)
    }
}

// First frame rendering
window.requestAnimationFrame(step)

requestAnimationFrame advantages

In addition to precisely controlling the call timing, requestAnimationFrame has two advantages:

  • When running in the Background tab or hidden iframe, requestAnimationFrame() pauses the call to improve performance and battery life.
  • Function throttling: in high-frequency events (resize,scroll, etc.), in order to prevent multiple function executions in a refresh interval, use requestAnimationFrame to ensure that the function is executed only once in each refresh interval.

cancelAnimationFrame

Cancel an animation frame request previously returned by calling the window.requestAnimationFrame() method.

const animateDiv = document.querySelector('.animate-div')
const requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
  window.webkitRequestAnimationFrame || window.msRequestAnimationFrame

const cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame

let start = null
let myReq = null

function step(timestamp) {
  let progress = timestamp - start
  animateDiv.style.left = progress + 'px'
  if (progress < 2000) {
    myReq = requestAnimationFrame(step)
  }
}
myReq = requestAnimationFrame(step)

setTimeout(() => {
  window.cancelAnimationFrame(myReq)
}, 200)

graceful degradation

There is still a compatibility problem with the requestAnimationFrame. Use requestAnimationFrame polyfill To downgrade gracefully.

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

(function() {
    'use strict';
    
    let vendors = ['webkit', 'moz'];
    for (let i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {
        let 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) {
        let lastTime = 0;
        window.requestAnimationFrame = function(callback) {
            let now = Date.now();
            let nextTime = Math.max(lastTime + 16, now);
            return setTimeout(function() { callback(lastTime = nextTime); },
                              nextTime - now);
        };
        window.cancelAnimationFrame = clearTimeout;
    }
}());

reference material

Posted on Fri, 26 Nov 2021 03:46:08 -0500 by puREp3s+