Tweening Silverlight with JSTweener
Posted by Dennis on Feb 25, 2008 in Uncategorized • 5 commentsI do most of my ActionScript tweening with the easy-to-use Tweener class. Tweener has been ported to JavaScript so that means it can be used with Silverlight 1.0. This port is called JSTweener and its syntax is identical with Tweener for ActionScript.
The most basic tweening example looks like this:
JSTweener.addTween( myObject, { x: 30, y: 30, time: 1, transition: "linear" } );
In ActionScript 3.0 you’d normally do something like this to move an object along the x- and y-axis:
Tweener.addTween( myObject, { x: 30, y: 30, time: 1, transition: "linear" } );
With Silverlight 1.0 things are a bit different. When you create objects programmatically you can create a XAML string and transform that into an object. In order for it to be transformed, you need to add the correct XAML tags:
<Canvas Width="75" Height="26">
<Canvas.RenderTransform>
<TransformGroup>
<RotateTransform CenterX="37.5" CenterY="13" Angle="0"/>
<ScaleTransform ScaleX="1.0" ScaleY="1.0" />
<SkewTransform AngleX="0" AngleY="0" />
</TransformGroup>
</Canvas.RenderTransform>
<TextBlock Width="75" Height="26" />
</Canvas>
These properties cannot be used in the same way as you’d normally do with ActionScript. Unfortunately you have to write code that’s a little more elaborate:
// -- Change the left and top positions:
JSTweener.addTween(object, {
"Canvas.Left": 100,
"Canvas.Top": 200,
.. etc ..
// -- Change the rotation
var transformGroup = object.renderTransform;
var rotateTransform = transformGroup.children.getItem( 0 );
JSTweener.addTween( rotateTransform, {
angle: 45,
.. etc ..
// -- Change the scaling
var transformGroup = object.renderTransform;
var scaleTransform = transformGroup.children.getItem( 1 );
JSTweener.addTween( scaleTransform, {
scaleX: 1.5,
scaleY: 1.2,
.. etc ..
// -- Change the Skewing
var transformGroup = object.renderTransform;
var skewTransform = transformGroup.children.getItem( 2 );
JSTweener.addTween( skewTransform, {
angleX: 15,
angleY: 30,
.. etc ..
I’ve made an example that makes use of all the different transition types and transform properties. See it in action here. This is the JavaScript that is used:
if (typeof com == "undefined")
com = {};
if (typeof com.rozengain == "undefined")
com.rozengain = {};
com.rozengain.Tweening = function(){
this.control = null; // -- the host object
this.rootElement = null; // -- the xaml root element
this.fontSource = null; // -- this will hold the load font
this.displayObjects = []; // -- this array stores programmatically created xaml objects
this.tweens = []; // -- this array stores the different tweening functions
this.outputWindow = null; // -- the output div
this.tweenDuration = 2; // -- standard tween duration
this.transitionTypes = [ // -- JSTweener transition types
"easeNone", "easeInQuad", "easeOutQuad", "easeInOutQuad", "easeInCubic", "easeOutCubic", "easeInOutCubic",
"easeOutInCubic", "easeInQuart", "easeOutQuart", "easeInOutQuart", "easeOutInQuart", "easeInQuint", "easeOutQuint",
"easeInOutQuint", "easeOutInQuint", "easeInSine", "easeOutSine", "easeInOutSine", "easeOutInSine", "easeInExpo",
"easeOutExpo", "easeInOutExpo", "easeOutInExpo", "easeInCirc", "easeOutCirc", "easeInOutCirc", "easeOutInCirc",
"easeInElastic", "easeOutElastic", "easeInOutElastic", "easeOutInElastic", "easeInBack", "easeOutBack", "easeOutBack",
"easeOutInBack", "easeInBounce", "easeOutBounce", "easeInOutBounce", "easeOutInBounce"
];
var cl = this; // -- point to this object. this is used because we cannot use
// -- "this" inside the functions
/**
* Silverlight initialisation
*/
this.createSilverlight = function(){
cl.outputWindow = document.getElementById( "OutputWindow" );
Silverlight.createObjectEx({
source: "xml/Tweening.xaml",
parentElement: document.getElementById("SilverlightControlHost"),
id: "SilverlightControl",
properties: {
width: "900",
height: "600",
version: "1.0"
},
events: {
onLoad: cl.handleLoad
}
});
};
/**
* Onload event handler
*/
this.handleLoad = function(cntrl, usrContext, rootElem){
cl.control = cntrl;
cl.rootElement = rootElem;
cl.loadFonts();
};
/**
* Create a downloader for the font
*/
this.loadFonts = function(){
var downloader = cl.control.createObject("downloader");
downloader.addEventListener("completed", cl.fontDownloaded);
downloader.open("GET", "assets/WEDNESDA.TTF");
downloader.send();
};
/**
* Font loading completed
*/
this.fontDownloaded = function(sender, eventArgs){
cl.fontSource = sender;
cl.createObjects();
};
/**
* Programmatically create the xaml objects
*/
this.createObjects = function(){
for (var i = 0; i < 15; i++) {
var xamlFragment = '' +
'<Canvas Width="75" Height="26">' +
'<Canvas.RenderTransform>'+
'<TransformGroup>' +
'<RotateTransform CenterX="37.5" CenterY="13" Angle="0"/>' +
'<ScaleTransform ScaleX="1.0" ScaleY="1.0" />' +
'<SkewTransform AngleX="0" AngleY="0" />' +
'</TransformGroup>' +
'</Canvas.RenderTransform>' +
'<TextBlock Width="75" Height="26" />' +
'</Canvas>';
var displayObject = cl.control.content.createFromXaml(xamlFragment, true);
displayObject["Canvas.Left"] = 80 + (Math.random() * ( cl.rootElement.width - 160 ) );
displayObject["Canvas.Top"] = 80 + (Math.random() * ( cl.rootElement.height - 160 ) );
var textBlock = displayObject.children.getItem( 0 );
textBlock.text = String.fromCharCode( 65 + ( Math.random() * 25 ) );
textBlock.setFontSource( cl.fontSource );
textBlock.fontFamily = "Wednesday";
textBlock.foreGround = cl.getRandomColor();
textBlock.fontSize = "100";
cl.rootElement.children.add( displayObject );
// -- add the object to an array for easy access
cl.displayObjects.push( displayObject );
}
cl.initialiseTweens();
cl.doTween( null, null );
};
/**
* Perform a tween on a random object
*/
this.doTween = function()
{
var object = cl.getRandomObject();
cl.tweens[ parseInt( Math.random() * cl.tweens.length ) ]( object );
};
/**
* Create the tween function array
*/
this.initialiseTweens = function(){
cl.tweens = [
cl.positionTween,
cl.scaleTween,
cl.angleTween,
cl.skewTween
];
};
/**
* Tweens the object's positin
*/
this.positionTween = function( object, index )
{
var tweenType = cl.getRandomTweenType();
var left = 80 + parseInt( (Math.random() * ( cl.rootElement.width - 160 ) ) );
var top = 80 + parseInt( (Math.random() * ( cl.rootElement.height - 160 ) ) );
JSTweener.addTween(object, {
"Canvas.Left": left,
"Canvas.Top": top,
time: cl.tweenDuration,
transition: tweenType,
onComplete: cl.doTween
});
cl.out(
"Canvas Position",
"Transition type: " + tweenType,
"\"Canvas.Left\": " + left,
"\"Canvas.Top\": " + top
);
};
/**
* Tweens the object's angle
*/
this.angleTween = function( object )
{
var transformGroup = object.renderTransform;
var rotateTransform = transformGroup.children.getItem( 0 );
var tweenType = cl.getRandomTweenType();
var angle = parseInt( Math.random() * 360 );
JSTweener.addTween( rotateTransform, {
angle: angle,
time: cl.tweenDuration,
transition: tweenType,
onComplete: cl.doTween
});
cl.out(
"RotateTransform",
"Transition type: " + tweenType,
"angle: " + angle
);
};
/**
* Tweens the object's scale
*/
this.scaleTween = function( object )
{
var transformGroup = object.renderTransform;
var scaleTransform = transformGroup.children.getItem( 1 );
var tweenType = cl.getRandomTweenType();
var scaleX = 0.5 + parseInt( Math.random() * 2 );
var scaleY = 0.5 + parseInt( Math.random() * 2 );
JSTweener.addTween(scaleTransform, {
scaleX: scaleX,
scaleY: scaleY,
time: cl.tweenDuration,
transition: tweenType,
onComplete: cl.doTween
});
cl.out(
"ScaleTransform",
"Transition type: " + tweenType,
"scaleX: " + scaleX,
"scaleY: " + scaleY
);
};
/**
* Tweens the object's skewing
*/
this.skewTween = function( object )
{
var transformGroup = object.renderTransform;
var skewTransform = transformGroup.children.getItem( 2 );
var tweenType = cl.getRandomTweenType();
var angleX = parseInt( Math.random() * 60 ) - 30;
var angleY = parseInt( Math.random() * 90 ) - 45;
JSTweener.addTween( skewTransform, {
angleX: angleX,
angleY: angleY,
time: cl.tweenDuration,
transition: tweenType,
onComplete: cl.doTween
});
cl.out(
"SkewTransform",
"Transition type: " + tweenType,
"angleX: " + angleX,
"angleY: " + angleY
);
};
/**
* Returns a random tween type
*/
this.getRandomTweenType = function()
{
return cl.transitionTypes[parseInt(Math.random() * cl.transitionTypes.length)];
};
/**
* Returns a random programmatically created xaml object
*/
this.getRandomObject = function()
{
return cl.displayObjects[parseInt(Math.random() * cl.displayObjects.length)];
};
/**
* Returns a random argb color in hex format
*/
this.getRandomColor = function(){
var r = parseInt(Math.random() * 256).toString(16);
var g = parseInt(Math.random() * 256).toString(16);
var b = parseInt(Math.random() * 256).toString(16);
if (r.length == 0)
r = "00";
else
if (r.length == 1)
r = "0" + r;
if (g.length == 0)
g = "00";
else
if (g.length == 1)
g = "0" + g;
if (b.length == 0)
b = "00";
else
if (b.length == 1)
b = "0" + b;
return "#ff" + r + "" + g + "" + b;
};
/**
* Output to the OutputWindow div
*/
this.out = function()
{
var s = "";
for( var i=0; i < arguments.length; i++ )
{
s += arguments[ i ] + "<br/>";
}
cl.outputWindow.innerHTML = s;
}
/**
* Start the application
*/
this.createSilverlight();
};




I copied the source from your demo and built everything with no trouble, but the left image shows up as pure white instead of a blurred photo. I’ll try the zipped source to see if its any different.
I also try, but I did not raise problems. Probably not copied correctly?
Excellent man! Just what I needed. I can use this in all the games that I plan to create.
Hi,
That is a very useful piece of code. Is it possible to use the same in C# coding instead of javascript?
If you upgrade to Silverlight/WPF 3 or 4 you may want to check out an animation engine for easing. It’s called Artefact Animator - artefactanimator.codeplex.com. It doesn’t use storyboards but it gives you a simple syntax that mimics Tweener for Flash. It also uses the same code for the Silverlight and WPF version so the experience is consistent.