HomeGuidesRecipesDocumentationChangelog
Log InDiscussions
Discussions

Animations

Background

To create animations we can use window.requestAnimationFrame(). window.requestAnimationFrame takes a single argument, a callback which will be invoked before the next repaint.

This callback should contain any logic needed to update the animation. This function will be passed a single argument, the timestamp of the moment at which the callback was invoked. Within the callback, once the update to the animation has been made, window.requestAnimationFrame should be called again unless some condition has been met (e.g. a certain amount of time has elapsed).

window.requestAnimationFrame returns a unique identifier. This unique identifier can be passed to window.cancelAnimationFrame() to cancel the request (i.e. stop the animation).

Introduction

In this example, window.requestAnimationFrame is called with the step function. In the step function, the amount of time that has elapsed since the animation started is calculated, a box model is translated a small amount, and if less than 2 seconds have elapsed, window.requestAnimationFrame is called again.

Example

let player = window.player;
let start;
let startPos = 0; // Starting point in this animation is at the origin {0,0,0}
let endPos = 10; // End point will be 10 units along the x-axis
let totalDist = 10; // Box travels 10 units
let maxTime = 2000; // in milliseconds
function step(timestamp) {
 if (start === undefined) start = timestamp;
 const elapsed = timestamp - start;
 const elapsedPercent = elapsed / maxTime;

 const nodePos = player.scene.get({
   name: 'Moving Box',
   plug: 'Transform',
   property: 'translation',
 });
  // `Math.min()` is used here to make sure that the node stops at exactly endPos along the X axis.
 nodePos.x = Math.min(startPos + (totalDist * elapsedPercent), endPos);
 player.scene.set({ 
   name: 'Moving Box', 
   plug: 'Transform', 
   property: 'translation' 
 },
   nodePos
 );
 if (elapsed < maxTime) {
   // Stop the animation after maxTime has passed
   window.requestAnimationFrame(step);
 }
}

window.requestAnimationFrame(step);

Easing

The animation code above, played over a long enough time may look mostly smooth during the middle portion of the animation. However, the start and stop are unnatural for how objects move in the real world. Very rarely do objects start at full speed, maintain that exact speed, and stop abruptly. To simulate real-world motion, we recommend including an easing function within your calculations for the placement of the object at each frame.

An easing function will take one argument, the elapsedPercent of total time. The easing function will return a value between 0 and 1 that indicates what amount of the animation should be complete on the given frame. For commonly used easing functions see https://easings.net

Example:

Here is the same 10-unit box translation animation as before with the application of easeInOutCubic. Try checking the second box above or copying and pasting this code into your browser console and watch the box's movement in the window above. Note the acceleration and deceleration at the start and end of the animation.

let player = window.player
let start
let startPos = 0
let endPos = 10
let totalDist = 10
let maxTime = 2000 // in milliseconds
function step (timestamp) {
  if (start === undefined) start = timestamp
  const elapsed = timestamp - start
  const elapsedPercent = elapsed / maxTime
  // Adding animation percent to capture motion as defined by easing function
  const animPercent = easeInOutCubic(elapsedPercent)
  const nodePos = player.scene.get({
    name: 'Moving Box',
    plug: 'Transform',
    property: 'translation'
  })
  nodePos.x = Math.min(startPos + totalDist * animPercent, endPos)
  player.scene.set(
    { name: 'Moving Box', plug: 'Transform', property: 'translation' },
    nodePos
  )
  if (elapsed < maxTime) {
    window.requestAnimationFrame(step)
  }
}
//Define easing function
function easeInOutCubic (x) {
  return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2
}
window.requestAnimationFrame(step)