Animations

Animations are very difficult to program. You need to implement a timer, and keep track of the elapsed time between frames as well as put together a framework to handle the animation. Well, no worries, the ChemDoodle Web Components library contains an abstract AnimatorCanvas class that already handles this all for you! You can already see the ChemDoodle Web Components‘ animation framework in motion with the RotatorCanvas class.

The AnimatorCanvas class implements the necessary framework to produce high quality animations. To create your own animations, all you have to do is extend the AnimatorCanvas class. Extending canvases is discussed on the last page, and if you have not reviewed it yet, now is a good time to do so.

Let’s begin by analyzing the variables of the AnimatorCanvas class. There is one control variable, AnimatorCanvas.timeout, that defines the number of milliseconds between frame repaints, and will define a general framerate. This value is set to 33ms by default, which specifies that the component attempt to achieve a repaint every 33ms, or a framerate of about 30fps. There are two control functions, AnimatorCanvas.startAnimation() and AnimatorCanvas.stopAnimation() that simply just start and stop the animation, respectively. There is also a function, AnimatorCanvas.isRunning(), that will return true if the animation is currently running and false otherwise. Lastly there is an abstract function that needs to be defined in all children of AnimatorCanvas, called nextFrame().

To adequately describe the nextFrame() function, we need to briefly discuss animation theory. When we tell the AnimatorCanvas class to repaint every 33ms, it will try to achieve this as closely as possible. Given a perfect world, each repaint would be spaced exactly 33ms apart and we would know exactly how much time had passed simply by logging the number of repaint calls within a period. Well there are two reasons why this is not true in reality. The first reason is that the timer is accurate, but it is not precise. It will call a repaint on average about every 33ms. Sometimes the timeout will be 30ms and sometimes the timeout will be 35ms. Secondly, we are assuming that the computer instantly updates and redraws the frame, but the computer actually needs to do work to update the scene and repaint the frame, so this adds to lag between frame repaints. This problem is compounded on slow processors, such as on iPhones. Therefore, in addition to the AnimatorCanvas class maintaining a timer, it also keeps track of the last time a frame was painted. This way, it can provide the nextFrame() function with the real amount of time that has passed since it was last called, allowing you to correctly update the scene in time. So let’s take an in-depth look into how to implement the nextFrame() function:

1
2
3
4
5
6
7
8
9
// we define the nextFrame() function of the child class
// this function is sent a parameter, delta, that specifies the amount of time since the last call in milliseconds
ChildOfAnimatorCanvas.nextFrame = function(delta){
  // we can calculate the seconds that have passed if milliseconds are too small
  var seconds = delta/1000;
  // using the elapsed time, and the rates of change of the components of our scene, we can update accordingly
  this.updateScene(seconds);
  //we are done, the AnimatorCanvas class handles the repainting
}

Let’s continue with an example. We will create a child of the AnimatorCanvas class that simply displays a molecule bouncing from the top of the canvas to the bottom. We will call this a BouncingCanvas. We will send the constructor an additional parameter, called bouncingSpeed, that will define the rate the molecule bounces in pixels/sec. The source is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<script>
  BouncingCanvas = function(id, width, height, bouncingSpeed){
    this.bouncingSpeed = bouncingSpeed;
    var up = true;
    this.nextFrame = function(delta){
      var seconds = delta/1000;
      var distance = this.bouncingSpeed*seconds;
      var overshoot = 0;
      var mol = this.getMolecule();
      for(var i = 0, ii=mol.atoms.length; i<ii; i++){
        var a = mol.atoms[i];
        a.y+=(up?-1:1)*distance;
        if(up){
          overshoot = Math.min(a.y, overshoot);
        }else{
          overshoot = Math.max(a.y, overshoot);
        }
      }
      var reverse = false;
      if(up && overshoot<0){
        reverse = true;
        overshoot*=-1;
      }else if(!up && overshoot>this.height){
        reverse = true;
        overshoot = this.height-overshoot;
      }
      if(reverse){
        up = !up;
        for(var i = 0, ii=mol.atoms.length; i<ii; i++){
          mol.atoms[i].y+=overshoot;
        }
      }
      mol.check();
    }
    this.create(id, width, height);
  }
  BouncingCanvas.prototype = new ChemDoodle._AnimatorCanvas();
</script>

The code was long, but all we did was create the BouncingCanvas class, and set it to extend the AnimatorCanvas class. The majority of code written involved programming the animation update in the nextFrame() function. The implementation is simple, just calculate the distance that the molecule moved by multiplying seconds by bouncingSpeed, and then update the molecule accordingly. You can access a Canvas's molecule at any time through the Canvas.getMolecule() function. If the molecule went too high, or too low, then reverse the direction the molecule is moving and set it back. At the end we called the Molecule.check() function, which makes the molecule check itself to update label positioning and ring centering as well as other algorithms important to its display. The following is an instantiation of a BouncingCanvas.


Toggle Bounce

 

1
2
3
4
5
<script>
  var bouncing = new BouncingCanvas('bouncing', 200, 200, 40);
  bouncing.loadMolecule(ChemDoodle.readMOL('Molecule Name\n  CHEMDOOD01051109043D 0   0.00000     0.00000     0\n[Insert Comment Here]\n  6  6  0  0  0  0  0  0  0  0  1 V2000\n    0.0000    1.0000    0.0000   C 0  0  0  0  0  0  0  0  0  0  0  0\n   -0.8660    0.5000    0.0000   C 0  0  0  0  0  0  0  0  0  0  0  0\n   -0.8660   -0.5000    0.0000   C 0  0  0  0  0  0  0  0  0  0  0  0\n    0.0000   -1.0000    0.0000   C 0  0  0  0  0  0  0  0  0  0  0  0\n    0.8660   -0.5000    0.0000   C 0  0  0  0  0  0  0  0  0  0  0  0\n    0.8660    0.5000    0.0000   C 0  0  0  0  0  0  0  0  0  0  0  0\n  1  2  2  0  0  0  0\n  2  3  1  0  0  0  0\n  3  4  2  0  0  0  0\n  4  5  1  0  0  0  0\n  5  6  2  0  0  0  0\n  6  1  1  0  0  0  0\nM  END'));
  bouncing.startAnimation();
</script>

Notice that we ended the instantiation by calling the startAnimation() function. This will have the animation running by default. Just exclude this last statement if you do not want it running by default. Sometimes we may wish to provide additional controls to ChemDoodle Web Components, such as the Toggle Bounce button above. Because ChemDoodle Web Components are just HTML objects, we can easily create HTML controls to control them. To illustrate this, lets go over the code to toggle the bouncing:

1
2
3
4
5
6
7
8
9
<script>
  function toggleBounce(){
    if(bouncing.isRunning()){
      bouncing.stopAnimation();
    }else{
      bouncing.startAnimation();
    }
  }
</script>

It's very simple, we have access to the AnimatorCanvas.isRunning() function as well as AnimatorCanvas.startAnimation() and AnimatorCanvas.stopAnimation(). Therefore, we just create an if/else statement, and if the canvas is running, we stop the animation, otherwise we start it. Adding a button control to an HTML page involves the <button> tag. The following HTML shows how to set up the button to call the toggleBounce() function we just wrote.

1
<button onclick="toggleBounce();">Toggle Bounce</button>

We simply define the tag, and then bind the onclick event to the toggleBounce() function. Congratulations, you are now a master at using ChemDoodle Web Components.

You have been given all the necessary tools to create dynamic and powerful interfaces and graphics for your websites and web applications. If you feel up to it, take a look at some of the advanced tutorials. Please do contact us if you need anything. Enjoy!