Table of Contents
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.
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>
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>
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>
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>
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>
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>
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
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>
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>
Copyright © 2002-2007 Laszlo Systems, Inc. All Rights Reserved. Unauthorized use, duplication or distribution is strictly prohibited. This is the proprietary information of Laszlo Systems, Inc. Use is subject to license terms.