Getting Started With Concert.js

The below series of examples teaches the concepts and usage of Concert.js through working sample code with explanations, starting with the most basic concepts and progressing up to demonstrate some advanced uses. See the reference documentation for all the details of everything mentioned here and more.
 

Sequences and Transformations

The most basic conceptual pieces involved in using Concert.js are the Sequence and the Transformation.

A Transformation is a single change applied over time to a single feature of a single object. For instance, it could be a change in the height of a DOM element from 0px to 24px over 1000ms (1 second).

A Sequence is a whole series of changes applied to a collection of objects over time. It contains one or more transformations.

Anything we do with Concert.js will involve creating and working with a Sequence object.

What does a simple Concert.js animation look like?

let sequence = new Concert.Sequence();

sequence.addTransformations(
  {
    target: document.getElementById("HelloDiv"),
    feature: "height",
    unit: "px",
    applicator: Concert.Applicators.Style,
    keyframes: { times: [0, 2000], values: [0, 24] }
  });

document.getElementById("GoButton").onclick =
  function () { sequence.begin(); };

The above code does the following:

  • Creates a single new Concert.Sequence object.
  • Passes an object defining a single animated transformation into the sequence's addTransformations() method.
  • Upon clicking the "Go" button, calls the sequence's begin() method to run the animated sequence. We'll see later that there are several ways to run an animation or synchronize it to something else happening on the page, but begin() is the simplest-- it just runs the sequence from beginning to end, synchronized to the system clock.

Let's look at each of the properties of the object that defines a single animated feature (a Transformation). Later we'll see that it is not always necessary to specify every one of these properties, because we can set default values for them.

  • target: The object that will be modified by the animation. In this case, it is a div with id "HelloDiv" which we want to slowly reveal.
  • feature: Which property of the target object will be modified. In this case, it is the height property (of the above-mentioned div object).
  • unit: What unit is being applied with the property's numeric value when. Here it is "px", but it could be anything else applicable, such as "em", "%", or even null for animating things which don't require an appended unit string.
  • applicator: A function that tells the sequence how to apply the animation values. In this case, what is being modified is the target object's style, so we use a pre-defined applicator function for modifying CSS styles. More details on what this means and how this works in a later tutorial step.
  • keyframes: An object containing two arrays: times, which is a list of moments in a timeline (here measured in milliseconds), and values, a corresponding list of values to apply at those times. Intermediate values will be calculated automatically between those keyframes.
So in summary, what the above code says is to take the div with id "HelloDiv", and modify its height style, setting it to 0px at time 0, and smoothly animating it until it reaches 24px at time 2000 (that is, at 2 seconds). If you click the "Go" button, you'll see it work.

How about a sequence with more than one transformation?

let sequence = new Concert.Sequence(),
  box1 = document.getElementById("Box1");
				
let box1Transformations = 
  [
    {
      target: box1,
      feature: "left",
      unit: "px",
      applicator: Concert.Applicators.Style,
	  keyframes: { times: [0, 1000], values: [0, 265] }
    },
	
    {
      target: box1,
      feature: "top",
      unit: "px",
      applicator: Concert.Applicators.Style,
	  keyframes: { times: [0, 1000], values: [0, 65] }
    }
  ];

sequence.addTransformations(box1Transformations);

document.getElementById("GoButton").onclick =
  function () { sequence.begin(); };

As can be seen above, addTransformations() can take an entire array of transformation objects. Here we transform two different features (the top and left style properties of the target object), each one having its own independent movement. Note that the same could have been accomplished by calling addTransformations() once with each transformation object.

Shortcut: Multiple target features can be specified together in an array. This requires passing arrays of values as well. For instance, the below code would work exactly the same as the above, but is much more concise. Note that the value at time zero (as well as at time 1000) is an array. The first value is paired with the first feature ("left"), and the second value is paired with the second feature ("top").

let sequence = new Concert.Sequence(),
  box1 = document.getElementById("Box1");
				
let box1Transformations = 
  {
    target: box1,
    feature: ["left", "top"],
    unit: "px",
    applicator: Concert.Applicators.Style,
    keyframes: { times: [0, 1000], values: [[0, 0], [265, 65]] }
  };

sequence.addTransformations(box1Transformations);

document.getElementById("GoButton").onclick =
  function () { sequence.begin(); };

What about gaps in motion?

Normally, values are interpolated in between keyframes. If the goal instead is to have one animation segment, then a period of time with no animation, then another animation segment, that is possible as well. Watch how the two boxes behave differently:

let sequence = new Concert.Sequence(),
  box1 = document.getElementById("Box1"),
  box2 = document.getElementById("Box2");

let boxTransformations = 
  [
    {
      target: box1,
      feature: "left",
      unit: "px",
      applicator: Concert.Applicators.Style,
	  keyframes:
      {
        times: [0, 750, 1500, 2250],
		values: [0, 90, 180, 270]
      }
    },

    {
      target: box2,
      feature: "left",
      unit: "px",
      applicator: Concert.Applicators.Style,
	  keyframes:
      {
		times: [0, 750, null, 1500, 2250],
		values: [0, 90, null, 180, 270]
      }
    }
  ];

sequence.addTransformations(boxTransformations);

document.getElementById("GoButton").onclick =
  function () { sequence.begin(); };

Inserting a null in between keyframes has the effect of breaking the animation into separate segments. In the example above, the first box has continuous value interpolation between every keyframe and the one after it. The second box is animated continuously from time 0 to time 750, but then no new values are calculated and applied until hitting time 1500, at which point another animation segment begins. Do note that the times and values arrays do need to be the same length, and any nulls in one need to match nulls in the other.

If you want a lot of discontinous segments, however, you may wish to look at an alternate method of describing transformations:

Specifying Segments Rather Than Keyframes

For some purposes, it is more useful to think of animations as a series of segments, rather than as a continuous series of keyframes and corresponding values.

let sequence = new Concert.Sequence(),
  box1 = document.getElementById("Box1");
	
let boxTransformations = 
  [
    {
      target: box1,
      feature: "left",
      unit: "px",
      applicator: Concert.Applicators.Style,
      segments:
        [
          { t0: 0, t1: 1000, v0: 0, v1: 270 },
          { t0: 3000, t1: 4000, v0: 270, v1: 0 }
        ]
    },

    {
      target: box1,
      feature: "top",
      unit: "px",
      applicator: Concert.Applicators.Style,
      keyframes: { times: [1000, 2000, 3000], values: [0, 70, 0] }
    }
  ];

sequence.addTransformations(boxTransformations);

document.getElementById("GoButton").onclick =
  function () { sequence.begin(); };

The first object in the boxTransformations array above uses a new notation. Instead of using keyframes, it specifies segments. Each segment object in the array represents one single start and end point of animation. It contains two properties corresponding to start and end times, t0 and t1, respectively; and it contains two properties corresponding to start and end values, v0 and v1, respectively. As demonstrated above, it is perfectly fine to mix segment notation and keyframe notation in the same sequence.

One handy feature of segment notation is that various additional properties can be specified at the segment level, allowing each segment to have different characteristics, such as using different easing functions. (Easing functions are covered in the next step of this tutorial.)

Whether you use keyframe or segment notation makes no difference in how the animation runs. Choosing one or the other is mainly a function of which one is easier for the task at hand. This choice can be especially useful when programmatically generating animations, where objects and arrays are being assembled by code, and one of these notations may fit better than the other for any given application.

Note: Whether using keyframe or segment notation, do not try to add overlapping segments applying to the same object feature. For instance, consider an animation in which you've added a segment causing an element's "left" property to increase from 0 to 100 between time 0 and time 1000. Adding a second segment (whether by keyframe or segment notation) that differently adjusts the same element's "left" property between, say, time 500 and time 1500 does not make sense and results in undefined behavior.

Additional Details

Worth knowing: When adding transformations to a sequence, you can also add several modifiers to the way the values are calculated. For instance, the value applied in any given frame can be modified by a multipier, a modulo factor, rounded, or adjusted by a constant offset amount. For details, see the full Concert.js documentation pages.

Reference documentation links for items covered in this step of the tutorial: