Easing functions

When declaring animations with WPF, we are able to utilize a powerful capability that helps us to define more specialized animations. While we normally provide a start and end value for our animations and let WPF interpolate the intermediate values, there is a way that we can affect this interpolation process.

There are a number of mathematical functions that provide complex animation paths and are known as easing functions. For example, these can accurately replicate the movement of a spring, or the bounce of a ball.

We can simply declare the appropriate easing function within the EasingFunction property of the animation. Each easing function extends the EasingFunctionBase class and has its own specific properties. For example, the BounceEase element provides Bounces and Bounciness properties, while the ElasticEase class declare the Oscillations and Springiness properties.

All easing functions inherit the EasingMode property from the base class. This property is of the enumeration type EasingMode and gives us three options. The EaseIn option follows the normal mathematical formula associated with each easing function. The EaseOut option uses the inverse of the mathematical formula.

The EaseInOut option uses the standard formula for the first half and the inverse formula for the second half. While not strictly true, this can be somewhat thought of as EaseIn affects the start of the animation, EaseOut affects the end of the animation, and EaseInOut affects both the start and the end of the animation. Let's see an example of a bouncing ball animation to demonstrate this ability:

<Canvas> 
  <Ellipse Width="50" Height="50" Fill="Orange" Stroke="Black"  
    StrokeThickness="3"> 
    <Ellipse.Triggers> 
      <EventTrigger RoutedEvent="Loaded"> 
        <BeginStoryboard> 
          <Storyboard RepeatBehavior="Forever"> 
            <Storyboard Storyboard.TargetProperty="(Canvas.Top)"> 
              <DoubleAnimation Duration="00:00:3" From="0" To="200"> 
                <DoubleAnimation.EasingFunction> 
                  <BounceEase EasingMode="EaseOut" Bounces="10" 
                    Bounciness="1.5" /> 
                </DoubleAnimation.EasingFunction> 
              </DoubleAnimation> 
            </Storyboard> 
            <Storyboard Storyboard.TargetProperty="(Canvas.Left)"> 
              <DoubleAnimation Duration="00:00:3.5" From="0" To="200"
                DecelerationRatio="0.2" /> 
            </Storyboard> 
          </Storyboard> 
        </BeginStoryboard> 
      </EventTrigger> 
    </Ellipse.Triggers> 
  </Ellipse> 
  <Line Canvas.Top="250" Canvas.Left="25" X1="0" Y1="1.5" X2="225" Y2="1.5"
    Stroke="Black" StrokeThickness="3" /> 
</Canvas> 

 

Here, we have a Canvas panel that contains two shapes: an ellipse and a line. The line is simply to give the impression of the ground. The Ellipse element defines some basic appearance properties and then an EventTrigger element that starts our eased animation when the shape object is loaded. We have an outer Storyboard element that is set to repeat forever and contains two inner storyboards.

The first of these inner storyboards targets the Canvas.Top Attached Property using the Storyboard.TargetProperty, while the second targets its Canvas.Left Attached Property. Note that we do not need to specify the Storyboard.Target property value here, as the storyboard resides within the target element, which will be implicitly set as the target for us. Also, remember that we need to wrap the Attached Property name with its class name in brackets for this to work.

The first storyboard is responsible for the vertical movement of our ball and so this is the animation that we want to use the BounceEase function with. In order to utilize this functionality, we simply declare the BounceEase object within the DoubleAnimation.EasingFunction property and set the desired property values.

The Bounces property determines how many times the ball should bounce, or rebound off the lower extent of the animation. Note that this does not include the final half-bounce that this easing function will perform. The Bounciness property specifies how bouncy the ball is. Strangely, the higher this value is, the less bouncy the ball will be. Also note that this value must be positive.

As physics determines that the horizontal velocity of the ball should remain constant for the most part, we do not need to apply an easing function to the second animation. Instead, we have added a small value for its DecelerationRatio property, which nicely simulates the sideways friction on the ball.

As can be seen, it is very easy to take advantage of these mathematical formulae to greatly increase the movement of our animations. While there is not enough space in this book for us to cover all of these easing functions, it is well worth investigating them yourselves. Let's take a look at another example, to see how we can simulate the movement of a spring using the ElasticEase class:

<Rectangle Canvas.Top="250" Canvas.Left="25" Width="25" Height="50"  
  Fill="Orange" Stroke="Black" StrokeThickness="3"> 
  <Rectangle.Triggers> 
    <EventTrigger RoutedEvent="Loaded"> 
      <BeginStoryboard> 
        <Storyboard RepeatBehavior="Forever"> 
          <Storyboard Storyboard.TargetProperty="Height"> 
            <DoubleAnimation Duration="00:00:3" From="50" To="200"> 
              <DoubleAnimation.EasingFunction> 
                <ElasticEase EasingMode="EaseOut" Oscillations="6" 
                  Springiness="2" /> 
              </DoubleAnimation.EasingFunction> 
            </DoubleAnimation> 
          </Storyboard> 
        </Storyboard> 
      </BeginStoryboard> 
    </EventTrigger> 
  </Rectangle.Triggers> 
</Rectangle> 

In this example, we have a thin Rectangle element that simulates the movement of a coiled spring using an ElasticEase function. The Oscillations property specifies the number of times that the rectangle will grow and shrink over the lifetime of the animation effect and the Springiness property determines the stiffness of the spring, where larger values equal more springiness.

While the two demonstrated easing functions are rather specialized and unsuitable to use in many cases, the vast majority of the remaining functions are all variations on standard circular, or exponential curves, or curves that use the formula f(t) = tn, where n is either determined by the exact easing function used, or by the Power property of the PowerEase function.

For example, the QuadraticEase function uses the formula f(t) = t2, the CubicEase function uses the formula f(t) = t3, the QuarticEase function uses the formula f(t) = t4, the QuinticEase function uses the formula f(t) = t5, while the PowerEase function uses the formula f(t) = tn, where n is determined by its Power property.

Apart from these variations of the standard acceleration/deceleration curve, there is one final useful easing function named BackEase. This has the effect of overshooting its starting or ending From or To values, dependent upon the value of the EasingMode property, and then reversing back to it. This is one of the more usable easing functions, so let's see an example of a TextBox element sliding on screen:

<Canvas ClipToBounds="True"> 
  <TextBox Canvas.Top="50" Canvas.Left="-150" Width="150" Height="25"> 
    <TextBox.Triggers> 
      <EventTrigger RoutedEvent="Loaded"> 
        <BeginStoryboard> 
          <Storyboard Storyboard.TargetProperty="(Canvas.Left)"
            Duration="00:00:2" RepeatBehavior="Forever">
            <DoubleAnimation Duration="00:00:1" From="-150" To="50"> 
              <DoubleAnimation.EasingFunction> 
                <BackEase EasingMode="EaseOut" Amplitude="0.75" /> 
              </DoubleAnimation.EasingFunction> 
            </DoubleAnimation> 
          </Storyboard> 
        </BeginStoryboard> 
      </EventTrigger> 
    </TextBox.Triggers> 
  </TextBox> 
</Canvas> 

In this example, we start with a Canvas object that has its ClipToBounds property set to true. This ensures that elements that are outside the bounds of the canvas will not be visible. Inside the canvas, we have declared a TextBox control that is initially placed totally outside the bounds of the canvas and so it will be invisible.

When the control is loaded, the EventTrigger element will start the animation that targets the Canvas.Left Attached Property. Note that the duration on the storyboard is one second longer than the duration on the animation and so the storyboard will wait for one second after the animation has completed before restarting. This gives us time to appreciate the effect of the applied easing function.

The animation will slide the textbox to its ending position from its initial off-screen position. By using the BackEase function, the textbox will slightly slide past its ending position and then reverse back into it. The amount past its ending position that it will slide to is determined by the value of its Amplitude property, with higher values extending the overshoot distance.

While we have only discussed using these easing functions with From, By and To animations so far, it is also possible to use them with key-frame animations as well. There are a number of classes that follow the Easing<Type>KeyFrame naming convention, such as the EasingColorKeyFrame class. These classes have an EasingFunction property that enables us to specify which function to use:

<TextBlock Text="The operation was successful" Margin="20"> 
  <TextBlock.Triggers> 
    <EventTrigger RoutedEvent="Loaded"> 
      <BeginStoryboard> 
        <Storyboard Storyboard.TargetProperty="FontSize"> 
          <DoubleAnimationUsingKeyFrames Duration="00:00:2.5"> 
            <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="8" /> 
            <EasingDoubleKeyFrame KeyTime="0:0:1" Value="36"> 
              <EasingDoubleKeyFrame.EasingFunction> 
                <BounceEase EasingMode="EaseOut" Bounces="2" 
                  Bounciness="1.5" /> 
              </EasingDoubleKeyFrame.EasingFunction> 
            </EasingDoubleKeyFrame> 
            <EasingDoubleKeyFrame KeyTime="0:0:2" Value="8"> 
              <EasingDoubleKeyFrame.EasingFunction> 
                <ElasticEase EasingMode="EaseIn" Oscillations="2" 
                  Springiness="1.5" /> 
              </EasingDoubleKeyFrame.EasingFunction> 
            </EasingDoubleKeyFrame> 
            <EasingDoubleKeyFrame KeyTime="0:0:2.5" Value="36"> 
              <EasingDoubleKeyFrame.EasingFunction> 
                <BackEase EasingMode="EaseOut" Amplitude="2" /> 
              </EasingDoubleKeyFrame.EasingFunction> 
            </EasingDoubleKeyFrame> 
          </DoubleAnimationUsingKeyFrames> 
        </Storyboard> 
      </BeginStoryboard> 
    </EventTrigger> 
  </TextBlock.Triggers> 
</TextBlock> 

In this example, we animate the size of the text in a TextBlock element using a number of key-frames. This creates the kind of transition effect that we might see on lines of text in Microsoft PowerPoint presentations and could be suitable to use in an application that presents textual information to the user.

We start by targeting the FontSize property and specifying a total duration of two and a half seconds. Our first key-frame simply sets our starting font size at zero seconds and so we can use a DiscreteDoubleKeyFrame for that. The second key-frame is an EasingDoubleKeyFrame element with a BounceEase easing function and a duration, or key time, of one second.

Following that, we have another EasingDoubleKeyFrame element that lasts for one second, but this one uses an ElasticEase function. Finally, we finish with one further EasingDoubleKeyFrame element with a BackEase easing function and a duration of half a second. Note that we have used small values for the Bounces and Oscillations properties, to keep the animation more usable.

Using these easing functions with key-frames enable us to chain any number of them together to create more complicated animated effects. However, it is easy to go overboard and create effects that are too much, as can be seen by increasing the values set for the Bounces and Oscillations properties in this example. In reality, even the modest values used here could be considered to be too much for practical use.