About Animation
Nifty transitions between pages or elements make for nice
eye candy, but they are tricky to implement and are achieved with a variety of
transformations. For instance, visual changes in an element's opacity or
position can be accomplished by gradual shifts in the number value of the
element, thus animating the change. For instance, a number going from 0 to 100
can be used as the opacity value of an element, and used to animate a change in
appearance from transparent to opaque.
Luckily, the ASP.NET AJAX Futures CTP comes with several
built-in animations. They are all defined in a library called PreviewGlitz.js
(which is embedded in the Futures CTP assembly).
Since the animations reside in an external library, the
PreviewGlitz.js file must be included manually in any page that uses them. This
file also depends on the PreviewScript.js file, the "core" JavaScript
library for the CTP.
Author's Notes: For simplicity, all
the samples in this installment still use the same project, GlitzTest,
with the nearly same configuration for the server control, ScriptManager, in
each related web page.
For a clearer overview of the relationships between the
animations to be discussed here, let me give a simple sketch in Figure 1.
Figure 1: The hierarchical relationships of the
animations supported in Futures CTP
As is seen from Figure 1, Animation
is the root of all MS AJAX client-side animations all of which are derived from
it. With the mainly programming mode in this installment being declarative, we
only list the descriptor definition for the parent class Animation,
PropertyAnimation, and InterpolatedAnimation.
Listing 1: The descriptor blocks for all parent
animation classes
//the descriptor block for class Animation
Sys.Preview.UI.Effects.Animation.descriptor={
properties:[
{name:"duration",type:Number},
{name:"fps",type:Number},
{name:"isActive",type:Boolean,readOnly:true},
{name:"isPlaying",type:Boolean,readOnly:true},
{name:"percentComplete",type:Number,readOnly:true},
{name:"target",type:Object}
],
methods:[
{name:"play"},
{name:"pause"},
{name:"stop"}
]
};
//the descriptor block for class <span class=Bold>PropertyAnimation</span>
Sys.Preview.UI.Effects.PropertyAnimation.descriptor={
properties:[
{name:"property",type:String},
{name:"propertyKey"}
]
};
//the descriptor block for class <span class=Bold>InterpolatedAnimation</span>
Sys.Preview.UI.Effects.InterpolatedAnimation.descriptor={
properties:[
{name:"endValue",type:Object},
{name:"startValue",type:Object}
]
};
Since all the properties and methods in Listing 1 are
generally used to control an animation, we will not dwell on them but leave them
to be explained in each concrete demo. There is still one more word to be
added that, generally, the three parent classes listed above will not be used
alone to render some animating effects as with the common parent class
definition in OOP languages.
Now, let us chew upon the derivate sub-animations one by
one.
Using FadeAnimation—Example 1
By using FadeAnimation, we can achieve the effect that the
opacity of some layer on the web page changes gradually from 1 to 0 or conversely,
i.e. the fading in/out effect.
With the project GlitzTest (used in
Part 1) still open, you just right-click the project and add a new web page
named AjaxFadeAnimation.aspx. With a little
modification, we can get the final design-time snapshot as shown in the
following Figure.
Figure 2: The design-time snapshot for fading
in/out animation
Now, let us first look into the related HTML code.
Listing 2
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Scripts>
<asp:ScriptReference Assembly="Microsoft.Web.Preview"
Name="PreviewGlitz.js" />
<asp:ScriptReference Assembly="Microsoft.Web.Preview"
Name="PreviewScript.js" />
</Scripts>
</asp:ScriptManager>
<br />
<div class="h1">Fading in and out Animation Demo</div><br />
<input id="btnFadeOutAnimate" type="button" value="Fade Out"
style="width: 155px; height: 37px" language="javascript"
onclick="return FadeUsingFutures(true)" />
<input id="btnFadeInAnimate" style="width: 135px; height: 37px"
type="button" value="Fade In" language="javascript"
onclick="returnFadeUsingFutures(false)" /><br />
<div id="animationTarget"
style="width: 207px; height: 252px; background-color: #ffcc00">
<img src="img/lushan_1.jpg" style="width: 208px; height: 258px;" />
</div>
</form>
Here, we first add the necessary assemblies and the related
*.js files within </asp:ScriptManager> block. In the debugging process,
I find that without either *.js file you would achieve no animating results or
run into a lot of error info. So, this is a MUST-BE step.
Next, we defined two buttons with the ID being btnFadeOutAnimate and btnFadeInAnimate
and attached their onclick event handlers respectively.
Following these is a simple <img> label for testing the fading in/out
effect. Obviously, this is the crucial hide in the two event handlers. So,
let us hurry into the key JavaScript programming part (here we omit the
declarative mode; interested readers can use it as an exercise).
Listing 3
<script language="javascript" type="text/javascript">
Sys.Application.initialize();
//Get a handle to the animation target
var domElementVar = new Sys.UI.Control( $get("animationTarget") );
//Create an instance of the FadeAnimation .
var fadeAnimation = new Sys.Preview.UI.Effects.FadeAnimation();
//Set the Duration
fadeAnimation.set_duration( 0.3 );
//Set the Animation Target as a Sys.UI.Control object
fadeAnimation.set_target( domElementVar );
//Set the Maximum Opacity Value
fadeAnimation.setValue( 70 );
//frames to play per second
fadeAnimation.set_fps(45);
function FadeUsingFutures( fadeOut )
{
//Decide whether to Fadein or FadeOut
var fadeEffect = fadeOut ?
Sys.Preview.UI.Effects.FadeEffect.FadeOut :
Sys.Preview.UI.Effects.FadeEffect.FadeIn ;
//Hide / Show the appropriate button(s)
$get("btnFadeOutAnimate").style.display = fadeOut ? "none":"block";
$get("btnFadeInAnimate").style.display = fadeOut ? "block":"none";
//Set the Animation Effect ( FadeIn / FadeOut )
fadeAnimation.set_effect( fadeEffect );
//Play the Animation
fadeAnimation.play();
}
</script>
In the above code we have provided enough explanations. It
seems that everything would become smooth if you better knew the basic
animation-related API's. So, let us save some words for the next discussing
topic.
Using LengthAnimation—Example 2
Now comes another kinds of animation—LengthAnimation. The LengthAnimation
can be used to change some property of the target within the values ranging
from some start value to a largest value with a typical usage of altering the
length/width property of some control.
Note that you can also use this animation to change some
property of text data despite merely specifying the start and end values. The
following table lists the common properties supported by this kind of animation.
Table 1: Common properties supported by LengthAnimation
Property
|
Description
|
target
|
Specify the target to apply this animation to.
|
property
|
Specify which property of the target to be influenced.
|
startValue
|
Specify the start value of the property to be influenced.
|
endValue
|
Specify the end value of the property to be influenced.
|
unit
|
Specify the changing unit of the animation.
|
duration
|
Specify the time duration of the animation with the unit
being second.
|
fps
|
Gets/sets the fps property of the animation (25 by
default).
|
isActive
|
Is a boolean value to indicate whether the animation has
already started.
|
isPlaying
|
Is a boolean value to indicate whether the animation has
been playing.
|
percentComplete
|
Returns a value ranging from 0 to 100 indicating current
progress of the animation.
|
Now, let us look into a simple demo for this animation.
Right-click project GlitzTest and
add another new web page named LengthAnimation.aspx. With
a little modification, the final design-time web page should look like Figure
3.
Figure 3: The design-time snapshot for length
animation
The next Figure shows the snapshot in the middle course of
the animation.
Figure 4: The run-time snapshot for the middle
course length animation
As you can image, the final snapshot will show the largest
picture of the girl.
Let us look into the how-to. Listing 4 gives the HTML code
for page LengthAnimation.aspx.
Listing 4
//…omitted
<img id="i" src="img\girl1.jpg" width="100" />
<hr />
<input type="button" id="startButton" value="Start"/>
Note we have created a picture using the <img> label
with its initial value of width being 100 instead of using style
attribute to specify this property—this is very important for later we will be changing
the value of property width of our animation target.
Next, look into the really interesting xml-script
programming in Listing 5.
Listing 5
<script type="text/xml-script">
<page xmlns:script="http://schemas.microsoft.com/xml-script/2005">
<components>
<image id="i">
<behaviors>
<LayoutBehavior id="Label1Style" />
</behaviors>
</image>
<lengthAnimation id="lani"
target="Label1Style" property="width"
startValue="100" endValue="480" fps='25'
duration="5" >
</lengthAnimation>
<button id="startButton">
<click>
<InvokeMethodAction target="lani" method="play" />
</click>
</button>
</components>
</page>
</script>
Here, we first create an instance of Sys.Preview.UI.Image
using <image> label with it pointing to the factual HTML <img>
element with ID named "i." Next, we define a LayoutBehavior with ID named
"Label1Style" and then bind it to the image instance. Note this LayoutBehavior
behavior serves as the target of the later defined LengthAnimation. By
continuously changing the width of behavior Label1Style from 100px to 480px,
the size of the image will be changed accordingly. With the value of attribute
duration set to smaller value, the changing speed of the image will be increased;
otherwise decreased. Lastly, if value of property startValue
is larger than endValue, you will see the converse
animating effect.
Author's Notes: If we do not
introduce LayoutBehavior as the mediator, clicking the Start
button will only result in the picture's sudden disappearance. Does this seem
a bit strange or is it a bug? In the case of LengthAnimation, we currently
have nothing to resort to but to test, test and test…
Using NumberAnimation—Example 3
The features of animation NumberAnimation is quite similar
to those of LengthAnimation (with the same properties such as target,
property, startValue, endValue, duration, fps, isActive, isPlaying,
percentComplete, and so on) except for two differences. The first
difference lies in that the middle value for animation NumberAnimation to
change can be decimal fraction through property integralValues.
Thus, in certain scenarios such as dealing with the length unit—meter, proper
use of animation NumberAnimation will result in better animating effect. The
second difference lies in that LengthAnimation supports pixel-based change of
the animation (through property unit) while NumberAnimation
does not.
Take a look at a vivid example to simulate some count down.
Still right-click project GlitzTest and add another new
web page named NumberAnimation2.aspx. (Note that in the
sample project GlitzTest I have also provided another
demo for NumberAnimation using a web page named NumberAnimation.aspx,
which is aimed to simulate the fading in/out effect by changing property value of behavior OpacityBehavior. For more info about it,
see the downloadable source code.) The following Figure shows the design-time
snapshot of page NumberAnimation2.aspx.
Figure 5: The design-time snapshot for number
animation
At the very beginning, an integer 30 is displayed on the
screen. With property integralValues set to false and
the continuing of the number animation, there appears decimal fraction on the
page as shown in Figure 6.
Figure 6: The run-time snapshot for the number
animation in the middle course
Here, on the page we place an <span> element to hold
the content to be counted down and a button to trigger the animating process. For
brevity, we still only cover the xml-script part as shown in Listing 6.
Listing 6
//…omitted
<components>
<label id="mynumber" />
<numberAnimation id="mynumberAnimation"
target="mynumber" property="text"
startValue="30" endValue="0" integralValues="false" duration="30" />
<button id="startButton">
<click>
<InvokeMethodAction target="mynumberAnimation"
method="play" />
</click>
</button>
</components>
In the above script, we first associate a <span>
element named mynumber with a MS AJAX client-side Label control. Note control Label
provides a property named text the value of which will
be shown within the related <span> element. Next, a node named numberAnimation (with case insensitive) is created with its
property target pointing to the label mynumber,
property property set to text so that the animating
content will be shown inside control mynumber. Then we
set two relevant properties startValue and endValue to 30 and 0, respectively. With property integralValues set to false the web
page shows some decimal fraction content; while when set to true,
we can vividly simulate a counting down stopwatch.
In a word, by using animation NumberAnimation, we can easily
and continuously change some values and associate this process with some page
elements on the page.
Using DiscreteAnimation—Example 4
Now, let us discuss another kind of animation—DiscreteAnimation.
On the one hand, there are some similarities with animations LengthAnimation
and NumberAnimation, such as within some specific time specifying the values
within some range in turn, etc. On the other hand, there are some dissimilarities
between them—in the case of animations LengthAnimation and NumberAnimation, we
only need to specify a start value and an end value and let the framework calculating
all the middle values. While for DiscreteAnimation, we must enumerate all the
values such as a…z, Sunday…Saturday, etc., and then it will play each of them
during the animating course. Here, for clarity and easy comparison, we also
list the common properties supported by DiscreteAnimation in the following table.
Table 2: Common properties supported by DiscreteAnimation
Property
|
Description
|
target
|
Specify the target to apply this animation to.
|
property
|
Specify which property of the target to be influenced.
|
values
|
List all the values to be played in the animation.
|
duration
|
Specify the time duration of the animation with the unit
being second.
|
fps
|
Gets/sets the fps property of the animation (25 by
default).
|
isActive
|
Is a boolean value to indicate whether the animation has
already started.
|
isPlaying
|
Is a boolean value to indicate whether the animation has
been playing.
|
percentComplete
|
Returns a value ranging from 0 to 100 indicating current
progress of the animation.
|
Nothing special here but the property values!
Examine a related example to achieve the discrete animating
effect. Still right-click project GlitzTest and add
another new web page named discrete.aspx. Figure 7
shows the design-time snapshot of page discrete.aspx.
Figure 7: The design-time snapshot for discrete animation
If you press F5 and launch this demo, you will see a
discrete animating process—the text of the label is changing from "Sunday"
until "Saturday" in discrete mode. For brevity, we only list the
xml-script part.
Listing 7
<script type="text/xml-script">
<page xmlns:demo="demo">
<components>
<label id="sampleLabel" />
<discreteAnimation id="wordAnimation"
target="sampleLabel" property="text"
values="'Sunday', 'Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday', 'Saturday'"
duration="3" />
<button id="startButton">
<click>
<invokeMethodAction target="wordAnimation"
method="play" />
</click>
</button>
</components>
</page>
</script>
In the above script, we first associate a <span>
element named sampleLabel with a MS AJAX client-side Label control which will serve as the target element to play
the animation. Next, a node named discreteAnimation is
created with its property target pointing to the label sampleLabel, property property
pointing to property text of control sampleLabel. The
most important property should be values, whose form is
a string composed of some optional elements separated by commas. Yes, as you
may have doped out, you can easily achieve the stopwatch animating effect as
accomplished by animation NumberAnimation by specifying a bunch of numbers. All
the other parameters and controls are quite similar to those of above
animations, so we do not chatter much any more.
Using CompositeAnimation—Example 5
From the literal definition, you can guess that this kind of
animation is used as a wrapper or holder to create more complex composite
animations. First, look over the source code from file PreviewGlitz.js since it
is pretty short.
Listing 8: The complete source code for CompositeAnimation
Sys.Preview.UI.Effects.CompositeAnimation=function(){
Sys.Preview.UI.Effects.CompositeAnimation.initializeBase(this);
this._animations=Sys.Component.createCollection(this)
};
Sys.Preview.UI.Effects.CompositeAnimation.prototype={
get_animations:function(){
return this._animations},
getAnimatedValue:function(){
throw Error.invalidOperation()},
dispose:function(){
this._animations.dispose();
this._animations=null;
Sys.Preview.UI.Effects.CompositeAnimation.callBaseMethod(this,"dispose")},
onEnd:function(){
for(var a=0;a<this._animations.length;a++)
this._animations[a].onEnd() },
onStart:function(){
for(var a=0;a<this._animations.length;a++)
this._animations[a].onStart()},
onStep:function(b){
for(var a=0;a<this._animations.length;a++)
this._animations[a].onStep(b)
}
};
Sys.Preview.UI.Effects.CompositeAnimation.descriptor={
properties:[{
name:"animations",type:Array,readOnly:true}]
};
Sys.Preview.UI.Effects.CompositeAnimation.registerClass(
"Sys.Preview.UI.Effects.CompositeAnimation",Sys.Preview.UI.Effects.Animation);
At the first blush, it performs no functions but to supply
an empty framework to hold sub-animations. However, the really significant and
interesting thing just lies in the "framework"—a container to encapsulate
any kind of sub-animations! Thus, method get_animations
becomes very important—we can use it to get the "handles" of sub-animations
and further to control each sub-animation. Actions speak louder than words;
let us build a demo to appreciate the mighty functions CompositeAnimation
holds.
As usual, right-click project GlitzTest
and add another new web page named CompositeAnimation2.aspx.
Figure 8 shows the final design-time snapshot of page CompositeAnimation2.aspx.
Figure 8: The design-time snapshot for composite
animation
Again appears the beautiful girl!? Yes, surely it is; but
here we will make the picture explode until finally disappearing from the
surface. Look into the HTML code of page CompositeAnimation2.aspx, as shown in
Listing 9.
Listing 9
<div class="h1">CompositeAnimation Demo</div><br />
<hr />
<div id='imageArea' class='demosample' style="width: 162px; height: 236px">
<img id="i" src="img/girl1.jpg" onclick="Explode('i');"
style="position: absolute; left: 16px; top: 131px; width: 157px;
height: 234px; cursor: hand;" />
</div>
<hr />
<div id='labelArea' class='demosample1' style="width: 265px; height: 21px">
<label id="Label1" >Click the girl to see what happens!</label>
</div>
Still nothing unusual, right? We first create a <img>
label and then a <label> element wrapped by the common <div> tag, nothing
more. But observant readers may already have figured out the answer to this
riddle—the img's click handler "Explode('i')." OK, let us see the
inner hidings as shown in Listing 10.
Listing 10
<script type="text/javascript">
//modify this number to change the scale factor
var _scaleFactor = 6;
var _image, _animation;
function Explode(id)
{
//to prevent from concurrent animating
if (_animation != null)
return;
_image = $get(id);
var width = parseFloat(_image.style.width);
var height = parseFloat(_image.style.height);
var x = parseFloat(_image.style.left);
var y = parseFloat(_image.style.top);
// Use a CompositeAnimation object to "explode" the target
// simultaneously with scaling, moving, and fading effect.
var NumberAnimation1 = new Sys.Preview.UI.Effects.NumberAnimation();
NumberAnimation1.set_target(_image);
NumberAnimation1.set_property('style');
NumberAnimation1.set_propertyKey('width');
NumberAnimation1.set_startValue(width);
NumberAnimation1.set_endValue(width * _scaleFactor);
var NumberAnimation2 = new Sys.Preview.UI.Effects.NumberAnimation();
NumberAnimation2.set_target(_image);
NumberAnimation2.set_property('style');
NumberAnimation2.set_propertyKey('height');
NumberAnimation2.set_startValue(height);
NumberAnimation2.set_endValue(height * _scaleFactor);
var NumberAnimation3 = new Sys.Preview.UI.Effects.NumberAnimation();
NumberAnimation3.set_target(_image);
NumberAnimation3.set_property('style');
NumberAnimation3.set_propertyKey('left');
NumberAnimation3.set_startValue(x);
NumberAnimation3.set_endValue(x - (width * (_scaleFactor - 1)) / 2);
var NumberAnimation4 = new Sys.Preview.UI.Effects.NumberAnimation();
NumberAnimation4.set_target(_image);
NumberAnimation4.set_property('style');
NumberAnimation4.set_propertyKey('top');
NumberAnimation4.set_startValue(y);
NumberAnimation4.set_endValue(y - (height * (_scaleFactor - 1)) / 2);
var FadeAnimation1 = new Sys.Preview.UI.Effects.FadeAnimation();
FadeAnimation1.set_target(new Sys.Preview.UI.Image(_image));
FadeAnimation1.set_effect (Sys.Preview.UI.Effects.FadeEffect.FadeOut);
_animation = new Sys.Preview.UI.Effects.CompositeAnimation();
_animation.get_animations().add(NumberAnimation1);
_animation.get_animations().add(NumberAnimation2);
_animation.get_animations().add(NumberAnimation3);
_animation.get_animations().add(NumberAnimation4);
_animation.get_animations().add(FadeAnimation1);
_animation.set_duration(0.3);
_animation.set_fps(35);
_animation.play();
//
var timer = new Sys.Preview.Timer();
timer.initialize();
timer.set_enabled(true);
timer.set_interval(500);
timer.add_tick(dispose);
}
function dispose(sender)
{
if (!_animation.get_isPlaying())
{
sender.set_enabled(false); // Disable the timer
_image.parentNode.removeChild(_image); // Remove the image
_animation = null;
}
}
</script>
Here, we first define three global variables as the
literalness self explained: _scaleFactor to specify the zooming in coefficient,
_image to point to the <img> element, and _animation to hold the instance
of class Sys.Preview.UI.Effects.CompositeAnimation. Next, when you click the
girl, the click event handler is triggered and function
Explode is called. To prevent concurrent animating, we
use an if-condition sentence to avoid multiple instances of class CompositeAnimation.
Then we obtain the required properties of the image. And then we create four
instances of NumberAnimation to achieve the scaling, moving, and fading effects
simultaneously. Next, a CompositeAnimation is created to piece together all
the sub-animations defined just now and configure the usual properties needed
to play the animation. At last, we start the animating. Note here we have
defined and invoked an instance of class Sys.Preview.Timer to do some cleaning
after the animating is over since the animation classes in PreviewScript.js do not
fire end events (please refer to the source code of PreviewGlitz.js). That is
over. Now you can press F5 to appreciate the exploding result, as shown in
Figure 9.
Figure 9: The run-time snapshot for the exploding
effect using CompositeAnimation
Pretty good! And if MS AJAX provided the rotating animation
for two-dimensional images, that would be perfect! However, it did not—it
relies on you the readers to realize this!
Author's Notes: Within the ASP.NET
AJAX Control Toolkit, there is an Extender control (which is an important kind
of approach to expand the Server controls with rich client-side capabilities)—AnimationExtender
by which we can more easily add animating effects to Web pages. However, there
is one shortcoming: it depends on the server-side ASP.NET framework. So, to
develop web applications on ASP.NET 2.0 platform you can borrow the animating
capacities of AnimationExtender, while to create browser-compatible web
applications using MS AJAX framework you will have to resort to the low-level
animation classes (covered here) provided in PreviewGlitz.js.