WynApse Home Page
Home    Blog    About    Contact       
Latest Article:


My Tags:
My Sponsors:






Lifetime Member:

Rolling the Silverlight Gear Animation By Michael Schwarz







Silverlight Beta 1 Intro

Originally written January 24, 2007 and updated to Silverlight Beta 1 on May 5, 2007. Beta 1 changes are noted below. I did not modify the manner in which this was accomplished in an effort to maintain the original tone of the article and get it to the site faster.

Original Text

Last week one day, a very nice WPF/E reflected image started popping up all over the web.

Today, Michael Schwarz took that reflection on as a project and posted this animation. He discusses it and has the code on his blog.

I stared at that for quite a while trying to figure out why I didn't think of doing that when I realized it would sure be cool if the gear would roll across the screen.

The truth is that didn't take all that long to do. What took the time was I also wanted it to roll back the other way, and I had no prior experience with synchronizing the two pieces of animation.

The counter clockwise gear rotation was simply reversing the sense on the RotateTransforms that Michael had already done. I tested this by modifying the existing xaml.

I wrapped the existing forward rotating gear and reflection in a canvas and gave it the name "MichaelsAnimation". I then duplicated that section, reversed the RotateTransform, and changed all names to be an obvious reversal of the initial names used such as "MichaelsAnimationReverse".

While trying to synchronize the two pieces, I found could start them off the canvas, and drive them off the canvas on the other side and it was very smooth, and no jerking at the edges, but that didn't take any real effort on my part, so what I really wanted was to have them start and end just at the canvas edge. This gives a discontinuity at the edges that I'll attribute to 'bouncing' off the wall... at least that's my story.

Synchronizing Forward and Reverse

The real magic for this is to use another canvas with a Storyboard that ran each of the other two animations. I need the rotating image in each direction of course, to give the impression of rolling, but I also need to wrap them in an outer animation that moves them across the canvas.

The outer canvas has a loaded= line that launches a javascript that sets the opacity on the reverse animation to 0, and starts the "Fwd" Storyboard.

The "Fwd" Storyboard has a Completed line that launches a javascript that sets the opacity on the forward animation to 0, the opacity on the reverse animation to 1, stops the forward animation, and starts the reverse.

The reverse animation has a similar function, so the whole thing simply repeats.

Once I found the code to help me with the synchronization, it was really quite easy!

I have the executable with full xaml exploded available here.

References

First an article by Karsten Januszewski discussing Animation handoff, and that article referenced one by Hans Hull that really had the information I was needing.

The Silverlight Beta 1 Changes

This is the first of the February CTP to Silverlight Beta 1 translations that has been a problem. It wasn't a big problem, but when you first run the page and nothing happens, you worry.

The problem was that it *almost* worked. I had some problems with starting the animation, so I looked at the What's new in Silverlight Beta 1 page, and saw that the BeginTime="1" hack should be removed. Well... that caused problems because now both animations were running when the canvas loaded, and it took a full cycle through to get everything stopped and started correctly, so each refresh would start with two forward animations, then it looked fine.

I had to dig deeper to keep that from happening, and the 'deeper' I found was the new <Canvas.Resources>>. Using Canvas.Resources, it's possible to have animations not happen until you need them, instead of setting the BeginTime to 1 day as we did with the previous CTPs.

So if you look at the xaml below, you'll find two Canvases near the top, each with a <Canvas.Resources> section that controls the forward and reverse animations that are detailed farther down. This is very similar to what the February code was like except the two Storyboard sections were wrapped in a <Canvas.Triggers> section that had a BeginTime of 1 day. Removing the 1 day meant they would free run using that xaml layout, hence the <Canvas.Resources> code.

I may as well have fought this battle here, because I use animations in many of my pages, so the <Canvas.Resources> sections are going to become very familiar.

JavaScript for producing this page:

<script>
function xaml_Loaded(sender, args) {
sender.findName("MichaelsAnimation").Opacity=1;
sender.findName("Fwd").begin();
}

function forward_completed(sender, args){
sender.findName("MichaelsAnimation").Opacity=0;
sender.findName("MichaelsAnimationReverse").Opacity=1;
sender.findName("Fwd").Stop();
sender.findName("Rev").Begin();
}

function reverse_completed(sender, args){
sender.findName("MichaelsAnimation").Opacity=1;
sender.findName("MichaelsAnimationReverse").Opacity=0;
sender.findName("Rev").Stop();
sender.findName("Fwd").Begin();
}
</script>


XAML for producing this page:

<Canvas 
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Loaded="xaml_Loaded" >

<!-- Canvas for forward rotation Canvas.Resources -->
<Canvas>
<Canvas.Resources>
<Storyboard x:Name="Fwd" Completed="forward_completed" >
<DoubleAnimation
Storyboard.TargetProperty="(Canvas.Left)"
Storyboard.TargetName="MichaelsAnimation"
From="0"
To="600"
Duration="0:0:5" />

</Storyboard>
</Canvas.Resources>
</Canvas>

<!-- Canvas for Reverse rotation Canvas.Resources -->
<Canvas>
<Canvas.Resources>
<Storyboard x:Name="Rev" Completed="reverse_completed" >
<DoubleAnimation
Storyboard.TargetProperty="(Canvas.Left)"
Storyboard.TargetName="MichaelsAnimationReverse"
From="600"
To="0"
Duration="0:0:5" />

</Storyboard>
</Canvas.Resources>
</Canvas>

<!-- Forward Animation -->
<Canvas x:Name="MichaelsAnimation" Opacity="0">

<Canvas.Triggers>
<EventTrigger>
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation x:Name="hourAnimation" Storyboard.TargetName="hourHandTransform" Storyboard.TargetProperty="Angle" From="0" To="360" Duration="0:0:5" RepeatBehavior="Forever" />
<DoubleAnimation x:Name="hourAnimation2" Storyboard.TargetName="hourHandTransform2" Storyboard.TargetProperty="Angle" From="0" To="360" Duration="0:0:5" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Canvas.Triggers>

<!-- Forward Gear -->
<Image Source="images/gear_large.png"
Canvas.Left="75" Canvas.Top="20">
<Image.RenderTransform>
<TransformGroup>
<RotateTransform x:Name="hourHandTransform" Angle="0" CenterX="61" CenterY="61"/>
<TranslateTransform X="0" Y="0"/>
</TransformGroup>
</Image.RenderTransform>
</Image>

<!-- Forward Reflected Gear -->
<Canvas
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Canvas.Left="75" Canvas.Top="198"
x:Name="test" Width="121" Height="121">

<Canvas.OpacityMask>
<LinearGradientBrush StartPoint="0.0,0.0" EndPoint="0.0,1.0">
<GradientStop Offset="0.0" Color="#CC000000" />
<GradientStop Offset="0.4" Color="#66000000" />

<GradientStop Offset="0.8" Color="#00000000" />
</LinearGradientBrush>
</Canvas.OpacityMask>

<Image Source="images/gear_large.png">
<Image.RenderTransform>
<TransformGroup>
<RotateTransform x:Name="hourHandTransform2" Angle="0" CenterX="61" CenterY="61"/>
<TranslateTransform X="0" Y="0"/>
</TransformGroup>
<ScaleTransform ScaleY="-0.5"/>
</Image.RenderTransform>
</Image>

</Canvas>

</Canvas>
<!-- MichaelsAnimation-->

<!-- MichaelsAnimationReverse -->
<Canvas x:Name="MichaelsAnimationReverse" Opacity="0">

<Canvas.Triggers>
<EventTrigger>
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard >
<DoubleAnimation x:Name="hourAnimationRev" Storyboard.TargetName="hourHandTransformRev" Storyboard.TargetProperty="Angle" From="360" To="0" Duration="0:0:5" RepeatBehavior="Forever"/>
<DoubleAnimation x:Name="hourAnimation2Rev" Storyboard.TargetName="hourHandTransform2Rev" Storyboard.TargetProperty="Angle" From="360" To="0" Duration="0:0:5" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Canvas.Triggers>

<!-- Reverse Gear -->
<Image Source="images/gear_large.png"
Canvas.Left="75" Canvas.Top="20">
<Image.RenderTransform>
<TransformGroup>
<RotateTransform x:Name="hourHandTransformRev" Angle="0" CenterX="61" CenterY="61"/>
<TranslateTransform X="0" Y="0"/>
</TransformGroup>
</Image.RenderTransform>
</Image>

<!-- Reverse Reflected Gear -->
<Canvas
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Canvas.Left="75" Canvas.Top="198"
x:Name="testrev" Width="121" Height="121">

<Canvas.OpacityMask>
<LinearGradientBrush StartPoint="0.0,0.0" EndPoint="0.0,1.0">
<GradientStop Offset="0.0" Color="#CC000000" />
<GradientStop Offset="0.4" Color="#66000000" />

<GradientStop Offset="0.8" Color="#00000000" />
</LinearGradientBrush>
</Canvas.OpacityMask>

<Image Source="images/gear_large.png">
<Image.RenderTransform>
<TransformGroup>
<RotateTransform x:Name="hourHandTransform2Rev" Angle="0" CenterX="61" CenterY="61"/>
<TranslateTransform X="0" Y="0"/>
</TransformGroup>
<ScaleTransform ScaleY="-0.5"/>
</Image.RenderTransform>
</Image>

</Canvas>

</Canvas>
<!-- MichaelsAnimationReverse -->

</Canvas>
Copyright © 2006-2017, WynApse