Animating Clipping Paths in Silverlight 2
Clipping paths provide a way to hide sections of an object based on different geometries. You can apply rectangles, ellipses or even create your own custom paths to a target object and hide specific areas from view. I enjoy experimenting with things from time to time and decided to try creating a sports score scroller similar to the one found at the top of http://www.espn.com to see how much could be done with pure XAML. It allows a user to scroll a set of scores left or right by moving their mouse over corresponding arrows.
While building the prototype I realized that I needed a rectangle animation to move a rectangle around to show and hide different parts of score objects. As the user scrolls left I would adjust the rectangle’s position to show the hidden area and then hide that area again as they scroll right. WPF provides a RectAnimation object but it’s not available in Silverlight unfortunately. That meant I had to use points instead and then use a PointAnimation to animate them (the points are shown below in the LineSegment elements). By doing that I can simulate a rectangle animation. Here’s an example of the main XAML for the sports score scroller:
<Canvas x:Name="cScroller" Height="45" Width="600" Background="#efefef" Visibility="Collapsed"> <Image x:Name="imgLeft" Source="/LeftArrow.jpg" Canvas.ZIndex="10" Canvas.Left="0" MouseEnter="imgLeft_MouseEnter" MouseLeave="imgLeft_MouseLeave" /> <Canvas x:Name="cScrollerItems" Height="45" Canvas.Left="10" Width="898"> <Image Source="/Scores1.jpg" Canvas.Left="0" /> <Image Source="/Scores2.jpg" Canvas.Left="596" /> <Canvas.Clip> <PathGeometry> <PathFigure IsClosed="true"> <LineSegment x:Name="PathSeg1" Point="0,0"/> <LineSegment x:Name="PathSeg2" Point="596,0"/> <LineSegment x:Name="PathSeg3" Point="596,45"/> <LineSegment x:Name="PathSeg4" Point="0,45"/> </PathFigure> </PathGeometry> </Canvas.Clip> </Canvas> <Image x:Name="imgRight" Source="/RightArrow.jpg" Canvas.ZIndex="10" Canvas.Left="605" MouseEnter="imgRight_MouseEnter" MouseLeave="imgRight_MouseLeave"/> </Canvas>
This XAML displays two JPG images containing scores and two JPG images representing arrows (yes…all of this could be done with XAML instead of images but my goal was to get something simple working quickly) but only shows 596 pixels total for the width of the score images. Anything to the right of that number is clipped due to the PathFigure that’s defined and corresponding LineSegment elements. If you’re new to PathGeomery and clipping, just imagine a rectangle starting at 0,0 that has a height of 45 and a width of 596. Everything to the right of the rectangle is clipped when the sports scroller first loads (in the image above that means everything to the right of the red right arrow is clipped).
As users mouse over one of the arrows I need to move the Canvas holding the sports score images (cScrollerItems) to the right or left. However, I also need to move the right line of the clipping rectangle (the line made from 596,0 to 596,45) right or left to show the hidden area of the image. Fortunately, this is easy to do with a Storyboard element and a PointAnimation. Here are the animations that are called as the user mouses over the left or right arrows:
<Storyboard x:Name="sbScrollerLeft"> <DoubleAnimation Storyboard.TargetName="cScrollerItems" Storyboard.TargetProperty="(Canvas.Left)" Duration="00:00:3" To="-293" /> <PointAnimation Storyboard.TargetName="PathSeg2" Storyboard.TargetProperty="Point" To="898,0" Duration="00:00:3" /> <PointAnimation Storyboard.TargetName="PathSeg3" Storyboard.TargetProperty="Point" To="898,45" Duration="00:00:3" /> </Storyboard> <Storyboard x:Name="sbScrollerRight"> <DoubleAnimation Storyboard.TargetName="cScrollerItems" Storyboard.TargetProperty="(Canvas.Left)" Duration="00:00:3" To="10" /> <PointAnimation Storyboard.TargetName="PathSeg2" Storyboard.TargetProperty="Point" To="605,0" Duration="00:00:3" /> <PointAnimation Storyboard.TargetName="PathSeg3" Storyboard.TargetProperty="Point" To="605,45" Duration="00:00:3" /> </Storyboard>
The following code is called to begin, pause and stop animations as the MouseEnter and MouseLeave events are fired:
private void imgLeft_MouseEnter(object sender, MouseEventArgs e) { this.sbScrollerLeft.Begin(); } private void imgLeft_MouseLeave(object sender, MouseEventArgs e) { this.sbScrollerLeft.Pause(); } private void imgRight_MouseEnter(object sender, MouseEventArgs e) { decimal left = GetItemsCanvasLeft(); Debug.WriteLine(left.ToString()); //Don't move anything if the ticker has never been moved left at this point if (left == _ItemsCanvasInitialLeftValue) return; this.sbScrollerRight.Begin(); } private void imgRight_MouseLeave(object sender, MouseEventArgs e) { this.sbScrollerRight.Pause(); } private decimal GetItemsCanvasLeft() { return decimal.Parse(this.cScrollerItems.GetValue(Canvas.LeftProperty).ToString()); }
All of this works great from a prototype perspective but you can see that specific points are hard-coded into the animation elements. My goal was to do as much as possible in XAML without writing C# code but with a little extra code it would be fairly straightforward to dynamically change the widths defined in the XAML based upon the size of the images being displayed (or width of text if that was used instead). A lot more could be done to enhance the scroller, but it’s nice to be able to create something like this with minimal code in a short period of time. An example of the scroller in action can be seen here (WMV video).