woensdag 20 juli 2011

Html + jQuery vs Silverlight: part 2. 3D Navbar with flying views

In theprevious post I gave a basic comparison between jQuery and Silverlight based on simple animations.  Today, I’ll be taking the animations a step further. 

I’m going to attempt to make a simple semi-3D UI with some animations, first in Silverlight and then in jQuery.  As I said in the previous post, I’m no jQuery expert so if there are simpler ways for doing the things I’m attempting here than just let me know.  Let’s get started.

Let me start by explaining the idea for the UI.  As always I’ll use the only tool I can call myself professional in… paint.



So the idea would be that Panel1 would rotate around its Y axis to provide a 3d like side panel (my paint skills proved inadequate so you’ll have imagine it).  The rotation should happen when the user starts up the page so we get a neat star trek / minority report like effect.  Then when one of the buttons is clicked, a view is loaded on the right side.  When the view comes into screen it will slide in from bottom to top.  If a view is already loaded then that view will slide away to the top.  

This is how the page translates in Silverlight:

At first we’ll need to create a page with a grid that has 2 columns:  one for the 3D panel and one for the View sliding in.  Since this is just a demo, I’ll be loading in a Usercontrol as a view instead of a whole page.
So in our main page we have the following code:

       <Grid x:Name="LayoutRoot" Background="White">
             <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Loaded" SourceName="stackPanel">
                          <ei:GoToStateAction StateName="End"/>
                    </i:EventTrigger>
             </i:Interaction.Triggers>
             <VisualStateManager.VisualStateGroups>
                    <VisualStateGroup x:Name="VisualStateGroup">
                          <VisualStateGroup.Transitions>
                                 <VisualTransition GeneratedDuration="0:0:3"/>
                                 <VisualTransition From="Begin" GeneratedDuration="0:0:3" To="End"/>
                          </VisualStateGroup.Transitions>
                          <VisualState x:Name="Begin"/>
                          <VisualState x:Name="End">
                                 <Storyboard>
                                       <DoubleAnimation Duration="0" To="-20" Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationY)" Storyboard.TargetName="stackPanel" d:IsOptimized="True"/>
                                       <DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationX)" Storyboard.TargetName="stackPanel" d:IsOptimized="True"/>
                                       <DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.CenterOfRotationX)" Storyboard.TargetName="stackPanel" d:IsOptimized="True"/>
                                       <DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.CenterOfRotationY)" Storyboard.TargetName="stackPanel" d:IsOptimized="True"/>
                                 </Storyboard>
                          </VisualState>
                    </VisualStateGroup>
             </VisualStateManager.VisualStateGroups>
             <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="150"/>
                    <ColumnDefinition Width="*"/>
             </Grid.ColumnDefinitions>
             <StackPanel x:Name="stackPanel" VerticalAlignment="Stretch" Background="#FFD4D4D4">
                    <StackPanel.Projection>
                          <PlaneProjection/>
                    </StackPanel.Projection>
                    <i:Interaction.Triggers>
                                 <i:EventTrigger EventName="Loaded">
                                       <ei:GoToStateAction TargetName="View1" StateName="Out">
                                       </ei:GoToStateAction>
                                       <ei:GoToStateAction TargetName="View2" StateName="Out">
                                       </ei:GoToStateAction>
                                       <ei:GoToStateAction TargetName="View3" StateName="Out">
                                       </ei:GoToStateAction>
                                 </i:EventTrigger>
                    </i:Interaction.Triggers>
                    <Button Margin="0,5,0,5" Content="Button1">
               
                          <i:Interaction.Triggers>
                                 <i:EventTrigger EventName="Click">
                                       <ei:GoToStateAction TargetName="View1" StateName="In">
                                       </ei:GoToStateAction>
                                       <ei:GoToStateAction TargetName="View2" StateName="Out">
                                       </ei:GoToStateAction>
                                       <ei:GoToStateAction TargetName="View3" StateName="Out">
                                       </ei:GoToStateAction>
                                 </i:EventTrigger>
                          </i:Interaction.Triggers>
               
                    </Button>
                    <Button Margin="0,5,0,5" Content="Button2">
                           <i:Interaction.Triggers>
                                 <i:EventTrigger EventName="Click">
                                       <ei:GoToStateAction TargetName="View1" StateName="Out">
                                       </ei:GoToStateAction>
                                       <ei:GoToStateAction TargetName="View2" StateName="In">
                                       </ei:GoToStateAction>
                                       <ei:GoToStateAction TargetName="View3" StateName="Out">
                                       </ei:GoToStateAction>
                                 </i:EventTrigger>
                          </i:Interaction.Triggers>
                    </Button>
                    <Button Margin="0,5,0,5" Content="Button3">
                                                    <i:Interaction.Triggers>
                                 <i:EventTrigger EventName="Click">
                                       <ei:GoToStateAction TargetName="View1" StateName="Out">
                                       </ei:GoToStateAction>
                                       <ei:GoToStateAction TargetName="View2" StateName="Out">
                                       </ei:GoToStateAction>
                                       <ei:GoToStateAction TargetName="View3" StateName="In">
                                       </ei:GoToStateAction>
                                 </i:EventTrigger>
                          </i:Interaction.Triggers>
                    </Button>
             </StackPanel>
             <local:viewControl x:Name="View1" Grid.Column="1" d:LayoutOverrides="Width, Height" Background="#FFFFB0B0"/>
             <local:viewControl x:Name="View2" Grid.Column="1" d:LayoutOverrides="Width, Height" Background="#FFE8FFAA"/>
             <local:viewControl x:Name="View3" Grid.Column="1" d:LayoutOverrides="Width, Height" Background="#FF89D0FF"/>
       </Grid>
</UserControl>

As you can see the 3D effect is very easy to do in Silverlight by transforming the control.  The animation can be simply triggered by a GoToStateAction, so no code required.  In this page there are only 2 states defined called begin and end.  These states are for the 3D effect in the side bar.

Then we create a Usercontrol that will represent our Views.  Below is the markup for the Usercontrol:

       <Grid x:Name="LayoutRoot" Width="250" Height="250" RenderTransformOrigin="0.5,0.5" Background="{Binding Background, ElementName=userControl}" DataContext="{Binding Source={StaticResource Text}}">
             <Grid.RenderTransform>
                    <CompositeTransform/>
             </Grid.RenderTransform>
             <VisualStateManager.VisualStateGroups>
                    <VisualStateGroup x:Name="VisualStateGroup">
                          <VisualStateGroup.Transitions>
                                 <VisualTransition GeneratedDuration="0:0:1"/>
                                 <VisualTransition From="Begin" GeneratedDuration="0:0:1" To="In"/>
                                 <VisualTransition From="In" GeneratedDuration="0:0:1.0001" To="Out"/>
                                 <VisualTransition From="Out" GeneratedDuration="0" To="Begin"/>
                                 <VisualTransition From="In" GeneratedDuration="0" To="Begin"/>
                          </VisualStateGroup.Transitions>
                          <VisualState x:Name="Begin">
                                 <Storyboard>
                                       <DoubleAnimation Duration="0:0:1.5" To="-600" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName="LayoutRoot" d:IsOptimized="True"/>
                                 </Storyboard>
                          </VisualState>
                          <VisualState x:Name="In"/>
                          <VisualState x:Name="Out">
                                 <Storyboard>
                                       <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName="LayoutRoot" d:IsOptimized="True">
                                              <LinearDoubleKeyFrame Value="600" KeyTime="0:0:1"/>
                                              <LinearDoubleKeyFrame Value="-600" KeyTime="0:0:1.0001"/>
                                       </DoubleAnimationUsingKeyFrames>
                                 </Storyboard>
                          </VisualState>
                    </VisualStateGroup>
             </VisualStateManager.VisualStateGroups>
       </Grid>

Again very easy markup.  We create three states for our animation. 
  • The Begin state will set the layoutroot to the bottom of the page where we wait for the animation.
  • The In state will be our layout in its original position
  • The Out state will be our layout root set to the top of our page
Now when we have our views with the appropriate state you can see in the markup of the page that we trigger a different state with each button.  For example on Button1 we want the first view to come in and the other views to go out. 

       <Button Margin="0,5,0,5" Content="Button1">
               
                          <i:Interaction.Triggers>
                                 <i:EventTrigger EventName="Click">
                                       <ei:GoToStateAction TargetName="View1" StateName="In">
                                       </ei:GoToStateAction>
                                       <ei:GoToStateAction TargetName="View2" StateName="Out">
                                       </ei:GoToStateAction>
                                       <ei:GoToStateAction TargetName="View3" StateName="Out">
                                       </ei:GoToStateAction>
                                 </i:EventTrigger>
                          </i:Interaction.Triggers>
               
                    </Button>

The total markup code for this example was approximately 160 lines.  As you can see, it’s clear that Silverlight was made for this kind of stuff.  The markup is easy to understand and even without blend you can build a very solid UI.  The beauty of this all is that you don’t have to write a single line of C# (or VB.NET) to trigger the animations.

Now let’s do the same in jQuery.

At first we’ll create our html:

    <div style="width: 500px; height: 400px;">
        <div id="SideBar" style="background: grey; height: 450px; width: 150px;">
            <button id="button1" style="width: 140px; margin: 10px 0px 10px 0px">
                Button1</button>
            <button id="button2" style="width: 140px; margin: 10px 0px 10px 0px">
                Button2</button>
            <button id="button3" style="width: 140px; margin: 10px 0px 10px 0px">
                Button3</button>
        </div>
        <div id="View1" style="width: 250px; height: 250px; background: lightblue; position: absolute;
            top: -800px; left: 200px;">
        </div>
        <div id="View2" style="width: 250px; height: 250px; background: lightgreen; position: absolute;
            top: -800px; left: 200px;">
        </div>
        <div id="View3" style="width: 250px; height: 250px; background: lightpink; position: absolute;
            top: -800px; left: 200px;">
        </div>
    </div>

So the html is no rocket science, now we need to apply the transformations.  To do this I cheated a little bit and I searched for a jQuery library that can do the 3D transformation for me. The one I ended up using is Rotate3Di .

So on the load of the document we start the animation that will rotate our panel.  Then when one of the buttons is clicked, we animate the div’s properly:

    <script type="text/javascript" language="javascript">
        $(document).ready(function () {
            $('#SideBar').rotate3Di(-15,3000);
        });
        $(":button").click(function () {
            if (this.id == "button1") {
                $("#View1").css("top", 800);
                $("#View1").animate({ top: "20px" }, 1500);
                $("#View2").animate({ top: "-=600px" }, 1500);
                $("#View3").animate({ top: "-=600px" }, 1500);
            }
            else if (this.id == "button2") {
                $("#View2").css("top", 800);
                $("#View2").animate({ top: "20px" }, 1500);
                $("#View1").animate({ top: "-=600px" }, 1500);
                $("#View3").animate({ top: "-=600px" }, 1500);
            } else if (this.id == "button3") {
                $("#View3").css("top", 800);
                $("#View3").animate({ top: "20px" }, 1500);
                $("#View1").animate({ top: "-=600px" }, 1500);
                $("#View2").animate({ top: "-=600px" }, 1500);
            }
        });
       
    </script>

To my surprise the Html and jQuery solution is smaller than the Silverlight one!  Only 44 lines!  The code does seem awfully similar to Silverlight.  This could be tribute to the fact that I first made the Silverlight application thus had the solution branded in my head, or it could be that they just do exactly the same thing besides expressing it in a different way, one in a more declarative way while the other in an imperative way.

So then why should you use Silverlight as it just brings in the overhead of installing a plugin and does not make your life easier?  Well there are a couple of reasons why in this example you would take Silverlight over the jQuery solution.

First of all, the rendering kind of sucks when doing it with jQuery.  Look at the two screenshots and tell me which looks better:


This is off course tribute to the fact that Silverlight has lots of fancy neat rendering tricks.

Another reason why Silverlight might be preference for this kind of stuff is because everything I did to achieve this was native to Silverlight.  I did not have to search for any third party scripts, though this could also be a problem because with Silverlight I am completely dependent on the implementation of Microsoft. 

The Silverlight solution is truly cross browser.  I have tried it on Chrome, Firefox and Internet Explorer and all of them render the solution exactly the same.  The jQuery solution isn’t.  On IE it would not work and the rendering was different on Chrome and Firefox.

Yet jQuery does have the big advantage by not needing any third party add-ins to run in your browser.  This might seem trivial compared to the disadvantages but it’s not.  Customers located in hospitals or other facilities, where an update requires a whole regime of acceptance tests, will be left in the cold if you develop this in Silverlight. 

The Html and jQuery solution also uses long proven technologies and with the forthcoming Windows 8 they aren’t likely to disappear soon.  Silverlight on the other is rather new and has an uncertain future.  It’s doubtful it will go away within a couple of years but its focus is shifting more and more away from the web.

That’s it for today, till next time :)

Note: files and examples will be up at the end of this week.

Geen opmerkingen:

Een reactie posten