Chapter 24. Animation

Table of Contents

1. Introduction
1.1. Preview of comparative examples
2. Absolute vs. relative animation
3. Using the from attribute
4. Using the motion attribute
5. Tags vs Script
6. Using the indirect attribute
7. Animator Groups
8. Animating Layout
9. Everything Animates

1. Introduction

OpenLaszlo applications have a fluid, dynamic feel that allow you to guide the user's experience. This fluidity is achieved with animators, which change the value of an object's attribute over a specified time duration. In this tutorial we'll start by showing the code for some short applications that allow you to see the essential simplicity of coding with animators. A little further down there are interactive examples that are a little more complicated but allow you to see animation in action.

Note that when we refer to animation in LZX applications, we mean anything that changes over time. This usually means something visible, but not always. For example, one might animate output volume of an audio file. You can animate the opening and closing of tabs and windows, the positions of items on the screen, and so forth.

For example, the following application defines an animator for a window that moves it to a position of x=100 over 1 second (1000 milliseconds). The animator is an instance of LzAnimator, and is defined by using the <animator> tag.

Example 24.1. A simple animator

<canvas height="100" width="500">
  <window>
    <animator attribute="x" to="100" duration="1000"/>
  </window>
</canvas>

An animator starts with the current value of an attribute unless otherwise specified. In the next example the application defines an animator for a window that first moves the window to a position of x="50", and then animates the window to a position of x="100" over 1 second.

Example 24.2. Specifying starting conditions

<canvas height="100" width="500">
  <window x="50">
    <animator attribute="x" from="50" to="100" duration="1000"/>
  </window>
</canvas>

Animators, by default, change an object's attribute by easing in and out of the from and to values. Running the above sample applications shows a window that accelerates in and out of its position. This nonlinear behavior can be minimally controlled with the use of the motion attribute. For example, the following code shows a window which moves linearly across the screen, in contrast to examples above. The values for the motion tag are: easeboth, easein, easeout, and linear.

Example 24.3. linear movement

<canvas height="100" width="500">
  <window>
    <animator attribute="x" to="100" duration="1000" motion="linear"/>
  </window>
</canvas>

Animators, like views, can be named so that they can be controlled later via scripts. This next example shows a common practice to activate an animator when a view is clicked, rather than when a view is first instantiated. Animators can be initially dormant by setting start="false", and then activated on demand by calling its start() method.

Example 24.4. Controlling animation with a script

<canvas height="100" width="500">
  <view bgcolor="red" width="100" height="100" onclick="this.myAnimator.doStart()">
    <animator name="myAnimator" attribute="x" to="100" duration="1000" start="false"/>
  </view>
</canvas>

Animators can also animate objects relative to their current values. This means that the goal of the animator is actually the "to" value plus the objects's current value. Starting with the example above, and including relative="true" to the set of attributes, creates a view that animates 100 pixels to the right every time it is clicked.

Example 24.5. Relative animation

<canvas height="100" width="500">
  <view bgcolor="red" width="100" height="100" onclick="this.myAnimator.doStart()">
    <animator name="myAnimator" attribute="x" to="100" duration="1000"
              start="false" relative="true"/>
  </view>
</canvas>

More than one animator can be added to an object, and the set will be processed simultaneously. The code below shows an object moving diagonally by using both "x" and "y" animators.

<canvas height="100" width="500">
  <view bgcolor="red" width="100" height="100" >
    <animator attribute="x" to="100" duration="1000"/>
    <animator attribute="y" to="100" duration="1000"/>
  </view>
</canvas>

To process the two animators above sequentially, i.e. one after the other, you use the <animatorgroup> tag, explained below.

1.1. Preview of comparative examples

The following examples illustrate various uses of the <animator> and <animatorgroup> tags. Notice that below each example there are "Test" and "Reset" buttons. The Test button causes the animation code to be executed; the Reset button brings the examples back to their initial state. Use these examples to see how different animation techniques compare to each other.

These applications rely on two support files, supportclasses.lzx and richbutton.lzx. References to these files and associated fonts are included in animation_library.lzx.

The following example shows the effect of animating the opacity, rotation, height, width, and x and y placement of simple views. Click on any red square to see the animation of the named attribute, or press "test" to see them all at once.

Example 24.6. Basic Animation

<canvas width="800" height="200" bgcolor="0xE5E1B0" >
  <!-- INCLUDES That provide framework for the example -->
  <include href="../animation_library.lzx"/>
  
  <!-- The EXAMPLE itself-->
  <view id="tutorial1" height="600" width="${parent.width}" visible="true">
    <ruler name="rlr" height="3000" y="0"/>
    <view height="30"/>
    <view height="120" y="30">
      <view x="50">
        <boxtitle label="opacity"/>
        <boxtitle label="rotation"/>
        <boxtitle label="width"/>
        <boxtitle label="height"/>
        <boxtitle label="x"/>
        <boxtitle label="y"/>
        <simplelayout axis="x" spacing="51"/>
      </view>
      <view y="20">
        <box id="B1" x="50" onclick="this.anm.doStart()">
          <animator name="anm" attribute="opacity"  to=".5"  duration="500" start="false"/>
        </box>
        <box id="B2" x="150" onclick="this.anm.doStart()">
          <animator name="anm" attribute="rotation" to="45"  duration="500" start="false"/>
        </box>
        <box id="B3" x="250" onclick="this.anm.doStart()">
          <animator name="anm" attribute="width"    to="99"  duration="500" start="false"/>
        </box>
        <box id="B4" x="350" onclick="this.anm.doStart()">
          <animator name="anm" attribute="height"   to="100" duration="500" start="false"/>
        </box>
        <box id="B5" x="450" onclick="this.anm.doStart()">
          <animator name="anm" attribute="x"        to="500" duration="500" start="false"/>
        </box>
        <box id="B6" x="550" onclick="this.anm.doStart()">
          <animator name="anm" attribute="y"        to="50"  duration="500" start="false"/>
        </box>
      </view>
      <simplelayout axis="y" spacing="5"/>
    </view>
    
    <!-- The TEST and RESET buttons -->
    <view x="50"   y="150" width="${parent.width - 50}">
      <richbutton label="Test" x="50" cap="rightsheer"
                  onclick="B1.anm.doStart();
                           B2.anm.doStart();
                           B3.anm.doStart();
                           B4.anm.doStart();
                           B5.anm.doStart();
                           B6.anm.doStart();
                           "/>
      <richbutton label="Reset" cap="leftsheer"
                  onclick="B1.setOpacity(1);
                           B2.setRotation(0);
                           B3.setWidth(49);
                           B4.setHeight(49);
                           B5.setX(450);
                           B6.setY(0);
                           this.parent.P2.setDisplay(' ');
                           "/>
      <simplelayout axis="x"/>
    </view>
  </view>
</canvas>

2. Absolute vs. relative animation

The following example shows the difference between relative and absolute animation. By default, relative is set to false, which means that animation of an attribute is calculated relative to that attribute's initial value. When relative is set to true, the animation is relative to the value of that attribute when the animator is invoked. Click on each of the red squares a few times to see the difference.

Example 24.7. Absolute and relative

<canvas width="800" height="200" bgcolor="0xE5E1B0" >
  <!-- INCLUDES That provide framework for the example -->
  <include href="../animation_library.lzx"/>
  
  <!-- RELATIVE ANIMATION EXAMPLE -->
  <view id="tutorial2" height="400" width="${parent.width}" visible="true">
    <ruler name="rlr" height="300" y="0"/>
    <view height="30"/>
    <view height="120">
      <view y="40" visible="true">
        <view>
          <text>Absolute</text>
            <box id="B7" x="50" onclick="this.anm.doStart()">
              <animator name="anm" attribute="x" to="200"  duration="500" start="false"/>
            </box>
          </view>
          
          <view>
            <text>Relative</text>
              <box id="B8" x="50" onclick="this.anm.doStart()">
                <animator name="anm" attribute="x" to="200"  duration="500"
                          start="false" relative="true"/>
              </box>
            </view>
          <simplelayout axis="y" spacing="5"/>
        </view>
      </view>
      
      <!-- The TEST and RESET buttons -->
      <view x="50" y="150">
        <richbutton label="Test" x="50" cap="rightsheer"
                    onclick="B7.anm.doStart();B8.anm.doStart()"/>
        <richbutton label="Reset" cap="leftsheer"
                    onclick="B7.setX(50);B8.setX(50);"/>
      <simplelayout axis="x"/>
    </view>
  </view>
</canvas>

3. Using the from attribute

The from attribute indicates the starting value from which the animation is to begin. If the object is not at that value, it first assumes it and then begins the animation. In the example below, the animated attribute is x, the horizontal placement. The from value in each case is 200, and the to value is 100. Before the specified animation begins, the object first moves by setting its x value to 100. In the absolute case, the object animates to x=100, where x is calculated relative to the object's initial value. In the second case, the object moves until x=100, where x is calculated relative to its value at the start of the animation.

Example 24.8. Using the "from" attribute

<canvas width="800" height="200" bgcolor="0xE5E1B0" >
  <!-- INCLUDES That provide framework for the example -->
  <include href="../animation_library.lzx"/>
  
  <!-- The EXAMPLE itself-->
  <view id="tutorial2" height="400" width="${parent.width}" visible="true">
    <ruler name="rlr" height="300" y="0"/>
    <view height="30"/>
    <view height="120" y="40">
      <view>
        <text>absolute</text>
        <box id="B9" x="50" onclick="this.anm.doStart()">
          <animator name="anm" attribute="x" from="200" to="100"
                    duration="500" start="false"/>
        </box>
      </view>
      
      <view>
        <text>relative</text>
        <box id="B10" x="50" onclick="this.anm.doStart()">
          <animator name="anm" attribute="x" from="200" to="100"
                    duration="500" start="false" relative="true"/>
        </box>
      </view>
      <simplelayout axis="y" spacing="5"/>
    </view>
  </view>
  
  <!-- The TEST and RESET buttons -->
  <view x="50" y="150">
    <richbutton label="Test" x="50" cap="rightsheer"
               onclick="B9.anm.doStart();B10.anm.doStart()"/>
    <richbutton label="Reset" cap="leftsheer"
               onclick="B9.setX(50);B10.setX(50);this.parent.P2.setDisplay(' ');"/>
    <simplelayout axis="x"/>
  </view>
</canvas>

4. Using the motion attribute

The motion attribute allows you to specify whether the animation accelerates ("ease out"), decelerates ("ease in"), both accelerates and decelerates ("ease both"), or is at a constant rate (linear).

Example 24.9. Using the "motion" attribute

<canvas width="800" height="300" bgcolor="0xE5E1B0" >
  <!-- INCLUDES That provide framework for the example -->
  <include href="../animation_library.lzx"/>
  
  <!-- The EXAMPLE itself-->
  <view id="tutorial2" height="400" width="${parent.width}" visible="true">
    <ruler name="rlr" height="300" y="0"/>
    <view height="30"/>
    <view height="200" y="40">
      <view>
        <text>easeboth</text>
        <box id="B11" x="50" onclick="this.anm.doStart()">
          <animator name="anm" attribute="x" to="600"
                    duration="1000" start="false" motion="easeboth"/>
        </box>
      </view>
      
      <view>
        <text>easein</text>
        <box id="B12" x="50" onclick="this.anm.doStart()">
          <animator name="anm" attribute="x" to="600"
                    duration="1000" start="false" motion="easein"/>
        </box>
      </view>
      
      <view>
        <text>easeout</text>
        <box id="B13" x="50" onclick="this.anm.doStart()">
          <animator name="anm" attribute="x" to="600"
                    duration="1000" start="false" motion="easeout"/>
        </box>
      </view>
      
      <view>
        <text>linear</text>
        <box id="B14" x="50" onclick="this.anm.doStart()">
          <animator name="anm" attribute="x" to="600"
                    duration="1000" start="false" motion="linear"/>
        </box>
      </view>
      <simplelayout axis="y" spacing="5"/>
    </view>
  </view>
  
  <!-- The TEST and RESET buttons -->
  <view x="50" y="260">
    <richbutton label="Test" x="50" cap="rightsheer"
                 onclick="B11.anm.doStart();
                          B12.anm.doStart();
                          B13.anm.doStart();
                          B14.anm.doStart()"/>
    <richbutton label="Reset" cap="leftsheer"
                 onclick="B11.setX(50);
                          B12.setX(50);
                          B13.setX(50);
                          B14.setX(50);
                          this.parent.P2.setDisplay(' ');"/>
    <simplelayout axis="x"/>
  </view>
</canvas>

5. Tags vs Script

In OpenLaszlo applications you can achieve the same result by using XML tags or JavaScript APIs. The examples above have demonstrated how to use the <animator> tag. Alternatively you can use the LzNode.animate() method in script statements. The following example contrasts tag and script approaches to animating views.

Example 24.10. The "bounce effect "

<canvas width="800" height="390" bgcolor="0xE5E1B0" >
  <!-- INCLUDES That provide framework for the example -->
  <include href="../animation_library.lzx"/>
  
  <!-- The EXAMPLE itself-->
  <view id="tutorial2" height="400" width="${parent.width}" visible="true">
    <ruler name="rlr" height="350" y="0"/>
    <view height="30"/>
    <view height="200" y="40">
      <box id="B15" x="50" onclick="this.ax1.doStart(); this.ax2.doStart()">
        <animator name="ax1" attribute="x" to="400"
                  duration="1000" motion="easeout" start="false"/>
        <animator name="ax2" attribute="x" to="300"
                  duration="1000" motion="easein"  start="false"/>
      </box>
      
      <box id="B16" x="50" onclick="this.ax1.doStart(); this.ax2.doStart()">
        <animator name="ax1" attribute="x" to="400"
                  duration="1000" motion="easein"  start="false"/>
        <animator name="ax2" attribute="x" to="300"
                  duration="1000" motion="easeout" start="false"/>
      </box>
      <view height="10"/>
      <box id="B17" x="50" onclick="this.bounce()">
        <method name="bounce">
          this.animate("x",400,1000,false,{motion:'easeout'});
          this.animate("x",300,1000,false,{motion:'easein'});
        </method>
      </box>
      <box id="B18" x="50" onclick="this.bounce()">
        <method name="bounce">
          this.animate("x",400,1000,false,{motion:'easein'});
          this.animate("x",300,1000,false,{motion:'easeout'});
        </method>
      </box>
      <box id="B19" x="50" onclick="this.bounce()">
        <method name="bounce">
          this.animate("x",400,1000,false,{motion:'easein'});
          this.animate("x",300,1000,false,{motion:'easeout'});
        </method>
      </box>
      <simplelayout axis="y" spacing="10"/>
    </view>
  </view>
  
  <!-- The TEST and RESET buttons -->
  <view x="50" y="350">
    <richbutton label="Test" x="50" cap="rightsheer"
                onclick="B15.ax1.doStart(); B15.ax2.doStart();
                         B16.ax1.doStart(); B16.ax2.doStart();
                         B17.bounce();  
                         B18.bounce();
                         B19.bounce();"/>
    <richbutton label="Reset" cap="leftsheer"
                onclick="B15.setX(50);
                         B16.setX(50);
                         B17.setX(50);
                         B18.setX(50);
                         B19.setX(50);
                  this.parent.P2.setDisplay(' ');"/>
    <simplelayout axis="x"/>
  </view>
</canvas>

6. Using the indirect attribute

The indirect attribute allows you to reverse the direction in which an animation occurs while still arriving at the same result. In the example below, setting the indirect attribute is set to true for the animation on B23. This causes the animation to go to the left, rather than the right, disappearing off the canvas, as it were, and reappearing on the right. Setting the indirect attribute to true in animating the rotation attribute changes the sense of the apparent motion from clockwise to counterclockwise. Try setting indirect to true on other properties — such as height, width, opacity — to see the effect.

Example 24.11. Indirect attribute

<canvas width="800" height="270" bgcolor="0xE5E1B0" >
  <!-- INCLUDES That provide framework for the example -->
  <include href="../animation_library.lzx"/>
  
  <!-- The EXAMPLE itself-->
  <view id="tutorial2" height="400" width="${parent.width}" visible="true">
    <ruler name="rlr" height="250" y="0"/>
    <view height="30"/>
    <view height="200" y="40">
      <view height="10"/>   
      <box id="B22" x="50" onclick="this.anm.doStart()">
        <animator name="anm" attribute="x" to="500"
                  duration="1000" start="false"/>
      </box>

      <box id="B23" x="50" onclick="this.anm.doStart()">
        <animator name="anm" attribute="x" to="500"
                  duration="1000" start="false" indirect="true"/>
      </box>
  
      <simplelayout axis="y" spacing="10"/>
    </view>
  </view>

  <!-- The TEST and RESET buttons -->
  <view x="50" y="250">
    <richbutton label="Test" x="50" cap="rightsheer"
               onclick="B22.anm.doStart();B23.anm.doStart()"/>
    <richbutton label="Reset" cap="leftsheer"
               onclick="B22.setX(50);
                        B23.setX(50);
                        this.parent.P2.setDisplay(' ');"/>
    <simplelayout axis="x"/>
  </view>
</canvas>

7. Animator Groups

The <animatorgroup> tag allows you to combine the actions of several animators on a single object. The animators can be set to operate simultaneously or sequentially. In the example below, animatorgroups are used to combine animations on the x and y attributes of the two red views. Notice that the x attribute of B20 has been set to linear motion instead of the default easeboth. In the case of B21 the motion of the y animation has been set to easeout. By clicking the "test" button you can see how the these simple changes to the animator parameters cause distinctly different movements.

The <animatorgroup> process attribute allows you to specify whether the animators within an <animatorgroup> should be processed sequentially or simultaneously. By default the actions are performed simultaneously. To see the difference, edit the example below by specifying process="sequential".

Example 24.12. Animator groups

<canvas width="800" height="400" bgcolor="0xE5E1B0"  >
  <!-- INCLUDES That provide framework for the example -->
  <include href="../animation_library.lzx"/>
  
  <!-- The EXAMPLE itself-->
  <view id="tutorial2" height="400" width="${parent.width}" visible="true">
    <ruler name="rlr" height="350" y="0"/>
    <view height="30"/>
    <view height="200" y="40">
      <view height="150" x="50">
        <box id="B20"  onclick="this.anm.doStart()">
          <animatorgroup name="anm" start="false" process="simultaneous">
            <animator attribute="x" from="0" to="300" duration="1000" motion="linear"/>
            <animator attribute="y" from="0" to="100" duration="1000"                />
          </animatorgroup>
        </box>
      </view>
      <view height="150" x="350">
        <box id="B21" onclick="this.anm.doStart()">
          <animatorgroup name="anm" start="false" process="simultaneous">
            <animator attribute="x" from="0" to="300" duration="1000"                 />
            <animator attribute="y" from="0" to="100" duration="1000" motion="easeout"/>
          </animatorgroup>
        </box>
      </view>
      <simplelayout axis="y" spacing="10"/>
    </view>
  </view>

  <!-- The TEST and RESET buttons -->
  <view x="50" y="350">
    <richbutton label="Test" x="50" cap="rightsheer"
                onclick="B20.anm.doStart();
                         B21.anm.doStart()"/>
    <richbutton label="Reset" cap="leftsheer"
                onclick="B20.setX(0);B20.setY(0);
                         B21.setX(0);B21.setY(0);
                         this.parent.P2.setDisplay(' ');"/>
    <simplelayout axis="x"/>
  </view>
</canvas>

Animatorgroups can be contained within animatorgroups, allowing for the encapsulation of more complex behavior. This next example illustrates that point by having the view return back to its original position. The return animates the object along both the x and y axes simultaneously after its initial movements on them separately.

Example 24.13. animatorgroup-2

<canvas height="210">
  <view bgcolor="red" width="100" height="100" onclick="this.outeranimatorgroup.doStart()">
    <text align="center" valign="middle">Click Me!</text>    
    <animatorgroup name="outeranimatorgroup" process="sequential" start="false">
      <animator attribute="x" to="100" duration="1000"/>
      <animator attribute="y" to="100" duration="1000"/>
      <animatorgroup  process="simultaneous" duration="1000">
        <animator attribute="x" to="0"/>
        <animator attribute="y" to="0"/>
      </animatorgroup>
    </animatorgroup>
  </view>
</canvas>

Note that in the code above there was no duration set for the x and y animators of the interior <animatorgroup>. Attributes such as duration, x, y, width, etc. can be defined in an <animatorgroup> and these values will be adopted by every subanimator unless they already have that attribute defined for themselves. If, as in the example above, a duration value is assigned to an animated group, it does not necessarily mean that the animatorgroup will be confined to that amount of time. It means that every subanimator may use that duration. If process='simultaneous' and each subanimator uses the group duration as their value then the length of total time for all subanimators would equal the duration time for the animatorgroup

8. Animating Layout

You can animate any attribute that can be modified at runtime. For example, the following example shows the effect you can get by animating a layout. Because layout encompasses several attributes we have simplified this example by having the layout return to its initial state — there is no "reset" button.

Example 24.14. Animating Layout

<canvas width="800" height="185" bgcolor="0xE5E1B0">
  <!-- INCLUDES That provide framework for the example -->
  <include href="../animation_library.lzx"/>
  
  <!-- The EXAMPLE itself-->
  <view id="tutorial2" height="200" width="${parent.width}" visible="true">
    <ruler name="rlr" height="150" y="0"/>
    <view height="30"/>
    <view height="60" y="40">
      <view id="V01" x="50">
        <borderedbox/>
        <borderedbox/>
        <borderedbox/>
        <borderedbox/>
        
        
        <simplelayout name="lyt" axis="x" spacing="5">
          <animatorgroup name="anm" start="false" process="sequential">
            <animator attribute="spacing" from="5"   to="50"  duration="1000"/>
            <animator attribute="spacing" from="50"  to="-49" duration="1000"/>
            <animator attribute="spacing" from="-49" to="5"   duration="1000"/>
          </animatorgroup>
        </simplelayout>
      </view>
    </view>
    
    <!-- The TEST button -->
    <view x="50" y="150">
      <richbutton label="Test" x="50" cap="both"
                  onclick="V01.lyt.anm.doStart()"/>
    </view>
  </view>
</canvas>

9. Everything Animates

Now that you've seen how animation works you can explore animating all different kinds of attributes. Here is a hint: try giving a view a "hidden" attribute, say "charm" that is constrained to the value of other attributes, and then animating "charm."

For example, often, you need a there-and-back animation. This can be a bother, since the obvious way to do it requires two animators, and adjusting properties within an animator group can be tricky.

A simple workaround is to use an animated value as the square root of the destination value. This gives you that kind of there-and-back behavior with a single animator.

Example 24.15. compound behavior in single animator

<canvas height="50">
  <view width="30" height="30" bgcolor="red"
        x="${225 - (this.sqrtx * this.sqrtx)}">
    <attribute name="sqrtx" value="-15"/>
    <animator attribute="sqrtx" from ="-15" to="15"
              duration="3000" repeat="Infinity"/>
  </view>
</canvas>