Canvas 2D Web Apps/Animations

This chapter explains how to use keyframe animations with cui2d. Specifically, it shows how to change numeric variables according to a predefined array of keyframes. If the changing variables are used in the process function as coordinates, colors, etc., the resulting graphics will appear to be animated. Of course, other ways of implementing animations with the canvas element exist, but the presented approach has some advantages and is extremely flexible.

The Example
The example of this chapter (which is available online; also as downloadable version) extends the example of the chapter on responsive buttons. Thus, the following sections only discuss the animation-specific parts of the code; see the chapter on responsive buttons and previous chapters for discussions of other parts.

In the example, three buttons are used to start three different animations. If clicked again, the first button just restarts the animation (even if it is already playing). The second button doesn't restart the animation if it is already playing. Lastly, the third button is inactive while its animation is playing and this state is also visually communicated by drawing it semitransparently, which achieves the effect of “graying out.”

Defining Animations
The example defines three keyframe animations for animating a single image:  animates the width and height of the image to create a wobbling effect;   is used to create an animated jump by changing the y coordinate of the image; and   is used to change the x coordinate and a rotation angle of the image to create a rocking motion:

Additionally, three keyframe arrays (one for each animation) are defined by these lines:

Each array is defined with the syntax  0th keyframe   1st keyframe   ...  . The first two arrays define the individual keyframes as objects with the properties ,  ,  , and. However, the  property of the 0th keyframe and the   property of the last keyframe is not required. is the time in seconds of the keyframe after the start of the animation. Note that keyframes have to be specified in ascending order of their times. and  define the velocities of change (i.e. the tangents or slopes) with which values are changing right before and right after each keyframe. The most important choices are: For faster or slower velocities, these numbers can be scaled; i.e. a value of 0.5 specifies half the velocity that the Catmull-Rom spline would use. A value of -3 specifies three times the velocity that a linear interpolation would use. In between the keyframes, a cubic Hermite spline is applied. (Catmull-Rom splines and linear interpolation are both special cases of the cubic Hermite spline.)
 * 0 for zero velocity, i.e. slow in/out (also known as ease in/out)
 * 1 for the velocity defined by a smooth Catmull-Rom spline (but it is only smooth if  and   of one keyframe are the same)
 * -1 for a constant velocity to the neighboring keyframe (also known as linear interpolation, but it will only be linear interpolation if the corresponding tangent of the neighboring keyframe is also specified by a -1)

The  property is an array of the actual data values at the keyframe. Their actual meaning (whether they are coordinates, color components, or sizes etc.) depends on how the animated values are actually used later on. In this example,  specifies keyframes only for the y coordinate of an image; therefore, the   properties of the keyframes are arrays of just one number, e.g.   for an array with one coordinate of value 200 pixels. animate an x coordinate and a rotation angle at the same time; therefore, the  properties are arrays of two elements. For example,  specifies an array of two elements, where the first one will be interpreted as an x coordinate of value 90 pixels and the second as an rotation angle of value -20 degrees.

The third array of keyframes  uses the constructor   to construct objects with the properties ,  ,  , and. It is up to you, which way of initializing keyframes you prefer.

Starting Animations
After animations have been defined as discussed in the previous section, they can be played (when processing events) with the function.

This function sets the properties,   and   with the specified arguments. In the example, the  arguments are ,  , and   for the three animations. The second argument (which determines the  property) is a factor to make the animation longer than defined by the keyframes (with a factor greater than 1), or shorter (with a factor less than 1). This is useful in order to change the overall speed of an animation without changing the time coordinates of all keyframes. The third argument allows to play the animation in an endless loop. (Which can be stopped with the function ).

In the example, the first button just restarts the animation  whenever the button is clicked: The second button checks whether the animation is not playing (with ) before restarting the animation: Thus, the animation cannot be restarted while it is playing. In a sense, this makes the button inactive while the animation is playing. To communicate inactive buttons, many GUIs “gray out” these buttons. The third button achieves a similar effect by using semitransparent rendering and only drawing the image : saves the current global alpha (which controls the opaqueness of the draw commands),  sets the global alpha to a rather low value, i.e. the opaqueness is strongly reduced, i.e. the following images are rendered almost transparently. After the button is drawn with, the global alpha is restored again with. Note that  is only called if   is , which just means that the button is inactive and doesn't react to events.

Animating Values
The values of the keyframes of an animation are animated (i.e. interpolated for the current time) with the function. The result of  is an array of values just like the ones that are specified in the array of keyframes but with the interpolated values for the current time. In the example, this is used this way:

Each call to  returns an array of the current values of animated values. From these arrays, individual elements are extracted and assigned to certain variables (here:,  ,  ,  , and  ). These variables are then used to draw the animated image. Note that by simply redrawing the canvas in every frame of the animation, complex animations are much easier to implement than if we had to individually modify all animated attributes of all animated objects.

The actual drawing of the animated image in the example is defined next: The most important line (and the only line that actually draws something) is this: It draws the image  centered at coordinates   and   at a size of   ×. (The coordinates  and   specify the top, left corner.) Since the values of ,  ,  , and   are all varying with time, this covers most of the animation. The rest of the code is only required to handle the rotation by.

To this end, we apply the line  which tells   to rotate all following drawings by. (The multiplication with  transforms a value in degrees to radians.) However, this rotation is always around the origin of the coordinate system. Thus, we have to translate (i.e. move) the pivot point of the rotation, which is at coordinates  and   in this example, to the origin of the coordinate system (i.e. to coordinates x=0 and y=0) in order to rotate around it. This is achieved with the line. After the rotation, we have to move the pivot point back to its original position with. If you look at the code, you'll see that these translations actually have to be specified in the reverse order. (If you want to read the transformations in the order they are specified, you have to think about how the coordinate system is transformed:  moves the origin of the coordinate system to our pivot point, then we rotate around the origin (at the new position of our pivot point), and then we move the origin back to its original position.)

Since we don't want this rotation to affect any further drawing commands, we have to restore the standard transformation again. The best way to do this, is to first save the current settings (i.e. state) of the context (which includes the transformation but also the fill color, font settings, etc.) with  and once we are done with drawing, these settings can be restored with.

This completes the discussion of how an application programmer would use the animation system of the example. The next section discusses, how it is implement in cui2d.js.

Implementing the Animation System
First, the constructors simply set the properties of the  and   objects:

As mentioned, the function  starts an animation and   stops the looping:

Basically,  just sets the properties   and. Both are times in milliseconds since January 1st, 1970. The values are based on the current time (for ) and additionally the   property of the last keyframe scaled by   (and converted from seconds after the start of the animation to milliseconds after January 1, 1970). Furthermore, the global variable  might be set if necessary. specifies the time (in milliseconds after January 1, 1970) when all animations are over. Thus, it only needs to be updated if the current animation is supposed to stop after all other animations. Lastly,  is called to request a redraw as soon as possible.

We also define a helper function  that returns   or   to specify whether a specific animation is currently playing (see above for examples how to use it):

In order to continuously repaint the canvas, the render loop is checking whether any animation is playing:

It checks whether any animation is playing by comparing  with the current time and updates the global variable   accordingly. Note that  is called when the animations just stopped. This is required because the canvas might change after an animation has stopped. If  is , a new frame is rendered by calling.

Apart from the actual computation of the animated values (which is discussed next), this completes the description of the animation system. Note that the system allows for restarting animations that are already playing, it allows for any number of animations playing at the same time, it allows for the animation of an unlimited number of values with a single array of keyframes, and it allows to employ arbitrary cubic Hermite splines for the animation of values while making it easy to specify slow in/out motions (by setting the  and   properties of specific keyframes to 0), Catmull-Rom splines (by setting all   and   properties to 1) and linear interpolation (by setting them all to -1). The last feature is implemented by the interpolation of values, which is discussed next.

The values of the keyframes in an animation are animated (i.e. interpolated for the current time) by the function. As mentioned, the result of  is an array of values just like the ones that are specified in the array of keyframes but with the interpolated values for the current time. The implementation of  basically evaluates a  cubic Hermite spline for every element of the   array with scaled slopes from Catmull-Rom splines or from linear interpolation (depending on the sign of the   and   properties). Unfortunately, the evaluation of splines is somewhat tedious and the mixing of Catmull-Rom splines with linear interpolation doesn't make it easier; thus, we are not going to discuss this code in detail.

However, there is an important feature at the start of the function: it first checks whether the animation hasn't started yet or whether the start and end time don't make sense (which is the case after they are both initialized to 0). In this case, the  array of the 0th keyframe is returned. Then it checks whether the animation is already over. In that case, the  array of the last keyframe is returned. This feature makes it possible to call  even if the animation is not playing and this is actually exploited in the example of the three animations described above.