Funky Popups using Tweener

Please note: There is an update to this post, Funky Popups using Silverlight 3, which describes how to achieve the same effect using the built-in easing functions of silverlight 3. However, this post remains a good desription of what easing does.

On a current project, we have some controls which have to pop up more info when clicked or using MouseOver. Because this is Silverlight, obviously we want it to look nice, so just switching Visibility from Collapsed to Visible doesn’t really cut the mustard. And I feel there are definite benefits from having some form of animation which conceptually links the box that pops up with the object you used to pop it up – this has been the case right back to the first Mac when new windows would grow and shrink accompanied by a simple animated border. I’ve been playing with various ways to animate these transitions, including using the Tweener class from the SilverlightContrib project.

In Silverlight we’ve got a lot of ways we can make information appear – sliding, fading in, scaling, and my default ‘appearance’ animation tended to be a simple fade combined with a linear scale.

Here’s a XAML for an illustration of this.


<UserControl x:Class="FunkyPopup.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0"
MouseEnter="simple_MouseEnter"
MouseLeave="simple_MouseLeave">
<Grid.Resources>
<Storyboard x:Name="simpleFadeBoardIn">
<DoubleAnimation
Storyboard.TargetName="simpleScaleTransform"
Storyboard.TargetProperty="ScaleX"
From="0.6"
To="1"
Duration="0:0:0.5"/>
<DoubleAnimation
Storyboard.TargetName="simpleScaleTransform"
Storyboard.TargetProperty="ScaleY"
From="0.6"
To="1"
Duration="0:0:0.5"/>
<DoubleAnimation
Storyboard.TargetName="simpleBorder"
Storyboard.TargetProperty="Opacity"
From="0"
To="1"
Duration="0:0:0.5"/>
</Storyboard>
<Storyboard x:Name="simpleFadeBoardOut">
<DoubleAnimation
Storyboard.TargetName="simpleScaleTransform"
Storyboard.TargetProperty="ScaleX"
From="1"
To="0.6"
Duration="0:0:0.5"/>
<DoubleAnimation
Storyboard.TargetName="simpleScaleTransform"
Storyboard.TargetProperty="ScaleY"
From="1"
To="0.6"
Duration="0:0:0.5"/>
<DoubleAnimation
Storyboard.TargetName="simpleBorder"
Storyboard.TargetProperty="Opacity"
From="1"
To="0"
Duration="0:0:0.5"/>
</Storyboard>
</Grid.Resources>
<Border VerticalAlignment="Center" HorizontalAlignment="Center" BorderThickness="3" BorderBrush="Black" Background="White" CornerRadius="5">
<TextBlock Margin="8" Text="Simple Fade and Scale"/>
</Border>
<Border
x:Name="simpleBorder"
VerticalAlignment="Center"
HorizontalAlignment="Center"
BorderThickness="3"
BorderBrush="Black"
Background="White"
CornerRadius="5"
RenderTransformOrigin="0.5,0.5">
<Border.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="simpleScaleTransform" ScaleX="0" ScaleY="0"/>
</TransformGroup>
</Border.RenderTransform>
<TextBlock Margin="8" Width="250" TextWrapping="Wrap" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque eu lacus in magna posuere sagittis. Curabitur vel odio nec enim mollis cursus. Etiam turpis. Nullam risus nisl, semper et, facilisis vitae, ultricies sed, quam. Sed pharetra scelerisque est. Vivamus lorem. Sed id elit sed mi ultrices feugiat. Mauris luctus tellus. Nullam vestibulum porta lorem. Sed congue pellentesque ipsum. Praesent dolor. Aliquam leo felis, euismod at, adipiscing at, auctor quis, urna."/>
</Border>
</Grid>
</Grid>
</UserControl>

What this does is put two Borders containing text in a grid column. One is the ‘control’ and the other is the information we want to pop up. The popup border has a name and a ScaleTransform with both scales set to 0. Then there are two storyboards, one to make the info pop up, the other to hide it again.

The storyboards animate the opacity, and also scale up from 0.7 to 1, giving a fairly pleasant effect when it appears. The transition takes half a second, a little longer than you’d actually want in real life, but this is intended to demonstrate the various options in  animating, rather than be a finished item. Slower transitions make it easier to see what’s happening.

The associated code-behind is here, and merely handles the MouseEnter and MouseLeave events, firing the appropriate storyboards.


using System.Windows.Controls;
using System.Windows.Input;

namespace FunkyPopup
{
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
}

private void simple_MouseEnter(object sender, MouseEventArgs e)
{
simpleFadeBoardIn.Begin();
}

private void simple_MouseLeave(object sender, MouseEventArgs e)
{
simpleFadeBoardOut.Begin();
}
}
}

If you run this code, you’ll get a border/text ‘control’. If you move the mouse over the control the info pane will pop up, and it will vanish when the mouse leaves the control. You’ll notice I set the RenderTransformOrigin property on the info grid so it scales from the centre, otherwise it scales from the top left of the grid.

So far, this is just a simple linear animation. Looks fine but there are some simple ways to do a bit more.

Tweening

A lot of animation makes use of tweening which is a process of taking the startpoint and endpoint of a desired animation and generating the steps in between. The whole Silverlight animation process does this for you whenever you use

DoubleAnimation

for example. It takes the From and To values, sees how long the animation should take, and each time a frame needs to be drawn, it mathematically calculates what the ‘in-between’ value should be – halfway through the animation the value will be the mean of the first and last value.

That’s fine for quite a lot of animation, but often you want something more – perhaps you want the animation to start quickly and slow down as it nears the end. Or vice versa. Silverlight has a built-in way to achieve some if these effects – see the SplineDoubleKeyFrame class for a pointer to how it works – but I’m looking at a different way of achieving these results.

What we want is a way of saying ‘Here’s the start point, and the end point, please animate between these points using a specific type of ‘in-between’ calculation. This is what the Tweener class does for you. I’ll show an example that would be difficult to do otherwise, then discuss what it’s doing.

To use the Tweener library, you’ll have to first download the SilverlightContrib dlls (get the source as well if you’re interested) and add a reference to the SilverlightContrib.dll in your project. Then in your XAML you’ll need to add a new namespace like so:


xmlns:tween="clr-namespace:SilverlightContrib.Tweener;assembly=SilverlightContrib"

I add a new ‘control’ to the test application, in the next grid column like so:


<Grid Grid.Column="1"
MouseEnter="bounce_MouseEnter"
MouseLeave="bounce_MouseLeave">
<Grid.Resources>
<Storyboard x:Name="bounceFadeBoardIn">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="bounceScaleTransform"
Storyboard.TargetProperty="ScaleX"
tween:Tween.From="0.6"
tween:Tween.To="1"
tween:Tween.Fps="30"
tween:Tween.TransitionType="EaseOutBounce"
Duration="0:0:0.5"/>
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="bounceScaleTransform"
Storyboard.TargetProperty="ScaleY"
tween:Tween.From="0.6"
tween:Tween.To="1"
tween:Tween.Fps="30"
tween:Tween.TransitionType="EaseOutBounce"
Duration="0:0:0.5"/>
<DoubleAnimation
Storyboard.TargetName="bounceBorder"
Storyboard.TargetProperty="Opacity"
From="0"
To="1"
Duration="0:0:0.5"/>
</Storyboard>
<Storyboard x:Name="bounceFadeBoardOut">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="bounceScaleTransform"
Storyboard.TargetProperty="ScaleX"
tween:Tween.Fps="30"
tween:Tween.TransitionType="EaseInBounce"
tween:Tween.From="1"
tween:Tween.To="0.6"
Duration="0:0:0.5"/>
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="bounceScaleTransform"
Storyboard.TargetProperty="ScaleY"
tween:Tween.Fps="30"
tween:Tween.TransitionType="EaseInBounce"
tween:Tween.From="1"
tween:Tween.To="0.6"
Duration="0:0:0.5"/>
<DoubleAnimation
Storyboard.TargetName="bounceBorder"
Storyboard.TargetProperty="Opacity"
From="1"
To="0"
Duration="0:0:0.5"/>
</Storyboard>
</Grid.Resources>
<Border VerticalAlignment="Center" HorizontalAlignment="Center" BorderThickness="3" BorderBrush="Black" Background="White" CornerRadius="5">
<TextBlock Margin="8" Text="Bounce Scale and fade"/>
</Border>
<Border
x:Name="bounceBorder"
VerticalAlignment="Center"
HorizontalAlignment="Center"
BorderThickness="3"
BorderBrush="Black"
Background="White"
CornerRadius="5"
RenderTransformOrigin="0.5,0.5">
<Border.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="bounceScaleTransform" ScaleX="0" ScaleY="0"/>
</TransformGroup>
</Border.RenderTransform>
<TextBlock Margin="8" Width="250" TextWrapping="Wrap" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque eu lacus in magna posuere sagittis. Curabitur vel odio nec enim mollis cursus. Etiam turpis. Nullam risus nisl, semper et, facilisis vitae, ultricies sed, quam. Sed pharetra scelerisque est. Vivamus lorem. Sed id elit sed mi ultrices feugiat. Mauris luctus tellus. Nullam vestibulum porta lorem. Sed congue pellentesque ipsum. Praesent dolor. Aliquam leo felis, euismod at, adipiscing at, auctor quis, urna."/>
</Border>
</Grid>

and the event handlers in the C# file:


private void bounce_MouseEnter(object sender, MouseEventArgs e)
{
bounceFadeBoardIn.Begin();
}

private void bounce_MouseLeave(object sender, MouseEventArgs e)
{
bounceFadeBoardOut.Begin();
}

If you run it, you’ll see that the info pops up quicker and gives a little bounce before it settles down into its 100% size and position. So what’s it doing? And how?

The key difference between the two storyboards is that the second ones are DoubleAnimationUsingKeyframes instead of the simpler DoubleAnimation. But you’ll also notice that I’m not supplying any keyframes as you would normally expect. That’s what Tweener does for you. I replaced the From and To properties with:


tween:Tween.Fps="30"
tween:Tween.TransitionType="EaseInBounce"
tween:Tween.From="1"
tween:Tween.To="0.6"

These are Attached Properties supplied by the Tween class, and what happens when these properties are attached to the DoubleAnimationUsingKeyframes timeline is that the Tweener library will generate the required keyframes for the required animation for you. It needs to know the From and To values, and you have to specify the number of frames per second to generate but this merely tells the tweener how many keyframes to generate – it doesn’t affect the ultimate smoothness of the animation since each generated keyframe will also have extra in-between frames generated for it by the Silverlight animation system. The really interesting one is the TransitionType. This specifies which equation to use out of a selection of possible ways to transition from one value to another. In this example we’re using the EaseOutBounce type for the appearance and EaseInBounce for the vanishing.

The list of types is fairly long (intellisense lists them) and their naming tells you something about them. EaseInXXX means the particular type of effect happens at the start of the animation. EaseOutXXX means it happens at the end (which is why we use EaseOutBounce for the popup appearing). You could try changing the transition type in the XAML to see what effect each of them gives. Generally the transitions are designed to ease the animation. A simple, linear animation looks very artificial. Items start and stop very suddenly, and there’s no sense of acceleration or deceleration. It’s this effect which easing is designed to solve. Most of the easing functions go straight from the first value to the last, with varying extremes of acceleration (and by using EaseInOutXXX you can get the acceleration both at the start and the end) and others, like the EaseOutBounce we used here, give a slightly funkier effect where the animation overshoots the endpoint, then bounces back and forth a little. It gives a more organic feel than a linear animation.

Some of these easing effects can be replicated using the KeySpline property of SplineDoubleKeyFrame, but it can be hard to know exactly what shape spline curve to use to achieve the required effect. These transitions originated in the Flash world, where they are quite commonly used, as it’s much easier to select the type of transition by name. Also, a simple spline can’t replicate the bouncy effects.

So, now we’ve got the box appearing with a little more wit, is there anything else we can do? Well, you can get some even nicer effects simply by playing with the timings of the animations, making the scaling asymmetric. I’ll add yet another of our ‘controls’. Here’s the full XAML for the new item:


<Grid Grid.Column="2"
MouseEnter="swish_MouseEnter"
MouseLeave="swish_MouseLeave">
<Grid.Resources>
<Storyboard x:Name="swishFadeBoardIn">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="swishScaleTransform"
Storyboard.TargetProperty="ScaleX"
tween:Tween.From="0.3"
tween:Tween.To="1"
tween:Tween.Fps="10"
tween:Tween.TransitionType="EaseOutBounce"
Duration="0:0:0.5"
BeginTime="0:0:0.2"/>
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="swishScaleTransform"
Storyboard.TargetProperty="ScaleY"
tween:Tween.From="0"
tween:Tween.To="1"
tween:Tween.Fps="10"
tween:Tween.TransitionType="EaseOutBounce"
Duration="0:0:0.5"/>
<DoubleAnimation
Storyboard.TargetName="swishBorder"
Storyboard.TargetProperty="Opacity"
From="0"
To="1"
Duration="0:0:0.1"/>
</Storyboard>
<Storyboard x:Name="swishFadeBoardOut">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="swishScaleTransform"
Storyboard.TargetProperty="ScaleX"
tween:Tween.Fps="30"
tween:Tween.TransitionType="EaseInBounce"
tween:Tween.From="1"
tween:Tween.To="0.3"
Duration="0:0:0.3"/>
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="swishScaleTransform"
Storyboard.TargetProperty="ScaleY"
tween:Tween.Fps="30"
tween:Tween.TransitionType="EaseInBounce"
tween:Tween.From="1"
tween:Tween.To="0"
Duration="0:0:0.3"
BeginTime="0:0:0.2"/>
<DoubleAnimation
Storyboard.TargetName="swishBorder"
Storyboard.TargetProperty="Opacity"
From="1"
To="0"
BeginTime="0:0:0.45"
Duration="0:0:0.05"/>
</Storyboard>
</Grid.Resources>
<Border VerticalAlignment="Center" HorizontalAlignment="Center" BorderThickness="3" BorderBrush="Black" Background="White" CornerRadius="5">
<TextBlock Margin="8" Text="Swish Scale and fade"/>
</Border>
<Border
x:Name="swishBorder"
VerticalAlignment="Center"
HorizontalAlignment="Center"
BorderThickness="3"
BorderBrush="Black"
Background="White"
CornerRadius="5"
RenderTransformOrigin="0.5,0.5">
<Border.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="swishScaleTransform" ScaleX="0.3" ScaleY="0"/>
</TransformGroup>
</Border.RenderTransform>
<TextBlock Margin="8" Width="250" TextWrapping="Wrap" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque eu lacus in magna posuere sagittis. Curabitur vel odio nec enim mollis cursus. Etiam turpis. Nullam risus nisl, semper et, facilisis vitae, ultricies sed, quam. Sed pharetra scelerisque est. Vivamus lorem. Sed id elit sed mi ultrices feugiat. Mauris luctus tellus. Nullam vestibulum porta lorem. Sed congue pellentesque ipsum. Praesent dolor. Aliquam leo felis, euismod at, adipiscing at, auctor quis, urna."/>
</Border>
</Grid>

And the inevitable event handlers in the csharp file:


private void swish_MouseEnter(object sender, MouseEventArgs e)
{
swishFadeBoardIn.Begin();
}

private void swish_MouseLeave(object sender, MouseEventArgs e)
{
swishFadeBoardOut.Begin();
}

This time I’ll delve a bit deeper into the slight changes I’ve made get the effect. What I wanted was for the info pane to appear as if it’s first being stretched vertically, then horizontally. So I needed to change the timing and duration of the scaling animations. Here’s the animation for the X scale factor when it’s appearing.


<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="swishScaleTransform"
Storyboard.TargetProperty="ScaleX"
tween:Tween.From="0.3"
tween:Tween.To="1"
tween:Tween.Fps="10"
tween:Tween.TransitionType="EaseOutBounce"
Duration="0:0:0.3"
BeginTime="0:0:0.2"/>

The key changes here are:

  • The From value starts at 0.3 instead of 0. If we left it at 0, the first part of the Y animation would be invisible because it would be stretching a zero-width block.
  • The BeginTime and Duration have been adjusted so that it doesn’t start until 0.2 secs into the animation, so the first part of the animation is a pure vertical stretch

<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="swishScaleTransform"
Storyboard.TargetProperty="ScaleY"
tween:Tween.From="0"
tween:Tween.To="1"
tween:Tween.Fps="10"
tween:Tween.TransitionType="EaseOutBounce"
Duration="0:0:0.5"/>

The Y stretch runs for the full half second, but it starts at 0 so the box does appear from nowhere.


<DoubleAnimation
Storyboard.TargetName="swishBorder"
Storyboard.TargetProperty="Opacity"
From="0"
To="1"
Duration="0:0:0.1"/>

The Opacity animation has been made much faster, so it completes almost immediately, so we get to see the full effect of the stretching. Without this, part of the initial appearance would not really be visible.

The inverse animation is fairly straightforward, it’s just a matter of inverting the timings, so we start the X scale immediately, but the duration remains 0.3s. The Y scale keeps its full duration (although you can vary the effect by playing with the length and start point of the Y scale as well). Because we’re scaling down, we use the EaseInBounce instead of the EaseOutBounce we used for the appearance.

One current disadvantage of using Tweener is that Blend doesn’t recognise it, so there’s no tool support for it. I have some ideas of ways around that, but this post is already far too long, so I’ll save those for another day.

Here’s the full source code for this project.

Advertisements

3 comments

    1. No idea where that went. I’ve put it back now.

      By the way, this article has been totally superseded by the easing functions in Silverlight 3. I’ll have to write an updated version which uses native SL3 code at some point.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s