If you have experience creating web animations using JavaScript libraries, you know that in its essence, these animations are defined by a selector which defines which DOM Elements will be affected by the animation, and a set of parameters, usually duration, CSS property and easing, that together the selector will provide the necessary information for the library to modify the CSS properties of certain elements over time.
Different libraries, have different parameters and behaviors but its fundamental parameterization is similar among all.
Many of libraries provide means to extend, either by user JavaScript coding or by means of plugins.
JQuery although isn’t an animation library per se, it has countless animation plugins in its ecosystem that animate web elements, but each has its own mechanism of timing and interaction.

ABeamer decouples its access to the DOM tree by using adapters, allowing ABeamer and plugins not only to animate CSS properties, but also SVG, Canvas, WebGL without extra complexity. Plugins can also be created to reduce the complexing of a CSS animation by mapping a ABeamer property into multiple CSS properties.
But instead of requiring that the user or the plugin to handle its own timing and interpolation mechanism, it provides Virtual Animators to handle the timing and interpolation of the plugin properties.

The support of Virtual Animators in ABeamer has been for quite a while but the version 1.1, it was added the class SimpleVirtualAnimator to simplify its usage, and it was added the optional frameRendered method to the adapters to reduce the render count when multiple animation properties are used.

Get started

To demonstrate how to use Virtual Animators, we will use the code pen example.
This example demonstrates how to animate Canvas using ABeamer.
Why use ABeamer instead of a timer?

  • ABeamer guaranties that each frame can generate a PNG file and it has the infrastructure to generate it.
  • The canvas animation can be part of a larger project that involves CSS animations.
  • ABeamer is bundle with a package fully of goodies: easings, oscillators, paths, formatters, …

The html code contains a simply canvas tag inside a scene.

<div class="abeamer-story" id=story>
  <div class="abeamer-scene" id=scene1>
    <canvas id="canvas" width="200" height="90"></canvas>
  </div>
</div>

For the coding part, I use TypeScript code, but JavaScript could also be used, and starts by extending the ABeamer.SimpleVirtualAnimator. This class manages the get/set of properties and implements the VirtualAnimator interface.

const canvas = document.getElementById('canvas') as HTMLCanvasElement;
const ctx = canvas.getContext('2d');

class CanvasAnimator extends ABeamer.SimpleVirtualAnimator {

  animateProp(name: ABeamer.PropName, value: ABeamer.PropValue): void {
    this.draw();
  }

  draw(): void {
    const w = canvas.width;
    ctx.clearRect(0, 0, w, canvas.height);
    const y = 80;

    ctx.font = `30px sans-serif`;
    ctx.fillStyle = this.props.colors;
    ctx.textBaseline = 'bottom';

    const text = this.props.text;
    const sz = ctx.measureText(text);
    ctx.fillText(text, (w - sz.width) / 2, y);

    const lineWidth = this.props.lineWidth;
    ctx.fillStyle = '#FF3333';
    ctx.fillRect((w - lineWidth) / 2, y + 2, lineWidth, 4);
  }
}

The class in the code above contains the method animateProp. This method will be called by ABeamer for each animation property, which in turn will execute draw to draw text and a line into the canvas. Further ahead, we will see that in this example is better to use animateProps instead of animateProp. But for now we will use this method, in order to understand the usage of animateProp.

In the code bellow, it creates an instance of this class, initializes with the initial values, and adds this virtual animator to the story by using story.addVirtualAnimator(ca).
The selector of the virtual animator is canvas-fx, and to distinguish from the CSS selectors, ABeamer uses the prefix % to determine that is a pick a virtual animator instead of CSS selector.
So when in the scene1.addAnimations is selected the %canvas-fx, ABeamer will use the CanvasAnimator to iterate the values of each animation property.
The usage is exactly the same as when a property represents a CSS property, but this time instead changing a CSS property value, it will call CanvasAnimator.setProp, and this if it’s not the uid property will call the animateProp, which in turn will update the canvas with the new value.

const texts = ['LIFE', 'IS AN', 'ENDLESS', 'JOURNEY'];
const colors = ['#333333', '#883333', '#338833', '#333388'];

const ca = new CanvasAnimator();
ca.selector = 'canvas-fx';
ca.props.lineWidth = 10;
ca.props.text = texts[0];
ca.props.colors = colors[0];
ca.draw();
story.addVirtualAnimator(ca);

scene1
  .addAnimations([{
    selector: '%canvas-fx',
    duration: '2s',
    props: [
      {
        prop: 'lineWidth',
        value: story.width - 10,
      },
      {
        prop: 'text',
        valueText: texts,
      },
      {
        prop: 'colors',
        valueText: colors,
      },
    ],
  }],
);

The above example fits well for a CSS animator or SVG animator where is required to update the each property individually, but in case of canvas and WebGL animators, most cases it’s only necessary to update once per frame if any property changed.
In the example above, the draw method will be called 3 times per frame, while once is enough.
To solve this problem, ABeamer 1.1 introduces a new optional method called frameRendered for the adapters, and SimpleVirtualAnimator uses this method to call animateProps once per frame if at least one property changed, except for the uid property.
So, in the example by changing the animateProp to animateProps, it will reduce the render count of 3 per frame to 1.

class CanvasAnimator extends ABeamer.SimpleVirtualAnimator {

  // disable this code
  // animateProp(name: ABeamer.PropName, value: ABeamer.PropValue): void {
  //   this.draw();
  // }

  animateProps(): void {
    this.draw();
  }

  // add the draw code
}

Using your own custom animators

If you want to use ABeamer just to render frames but you have your own animators, you can use a simple virtual animator just to trigger the next render frame.

$(window).on("load", () => {

  const story = ABeamer.createStory(/*FPS:*/20);

  const canvas = document.getElementById('canvas') as HTMLCanvasElement;
  const ctx = canvas.getContext('2d');

  class CanvasAnimator extends ABeamer.SimpleVirtualAnimator {

    // draw it will be called once for every frame.
    draw(): void {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      // since it's a 2s animation, running at 20 FPS.
      // story.renderFramePos will be a value from 0..39
      const text = story.renderFramePos;
      ctx.fillText(text, 0, 0);
    }
  }

  const ca = new CanvasAnimator();
  ca.selector = 'canvas-fx';
  ca.props.anime = 0;
  story.addVirtualAnimator(ca);

  scene1
    .addAnimations([{
      selector: '%canvas-fx',
      duration: '2s',
      props: [
        {
          prop: 'anime',
        },
      ],
    }],
  );

  story.render(story.bestPlaySpeed());
});